본문 바로가기
Java

[ Java ] 자바에서 구현 해볼 수 있는 로그인 기법-2 ( 스프링 시큐리티 )

by YBin's 2024. 9. 26.

지난 뽀-스트

https://udangtang-dev.tistory.com/6

 

[ Java ] 자바에서 구현 해볼 수 있는 로그인 기법-1 ( 세션 기반 로그인 )

이번 포스팅에서는 자바에서 써먹을 수 있는 로그인 방법을 끄적여 볼까 한다.1. Session-based Authentication세션 기반 로그인은 웹 애플리케이션에서 사용자의 인증 상태를 서버에 저장하고, 그 상태

udangtang-dev.tistory.com

 

 

이번에는 스프링 시큐리티를 사용해보고자 한다.

Spring Security는 Spring Framework의 강력한 보안 모듈로, 애플리케이션의 인증과 권한 부여를 관리한다.

기본적으로 사용자 정의가 가능한 보안 정책을 제공해서, 폼 로그인이나 OAuth2, JWT등은 쉽게 구현 가능하다. !

 

기본적인 흐름은 다음과 같다 ( 세션과 유사 )

  1. 로그인 요청:
      사용자가 브라우저에서 사용자 이름과 비밀번호를 입력하여 로그인 요청을 보냅니다.
  2. 스프링 시큐리티 필터:
      요청은 스프링 시큐리티의 필터 체인을 거칩니다. 이 과정에서 요청이 인증되었는지 확인합니다.
  3. 인증 처리:
      스프링 시큐리티가 설정한 UserDetailsService(사용자가 작성한 파일입니다.)를 통해 사용자의 정보를 로드하고, AuthenticationManager(이 또한 임의로 작성한 파일입니다.)가 사용자의 비밀번호를 검증합니다.
  4. 인증 성공/실패 처리:
      인증에 성공하면 사용자는 인증된 상태로 애플리케이션에 접근할 수 있으며, 인증 실패 시 로그인 페이지로 리다이렉트됩니다.
  5. 세션 관리:
      스프링 시큐리티는 세션을 관리하여 사용자의 인증 상태를 유지합니다.

 

프로젝트 셋업

Maven 또는 Gradle을 사용해 스프링 부트 프로젝트를 설정하고, spring-boot-starter-security를 추가

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId> <!-- Thymeleaf를 로그인 폼에 사용 -->
</dependency>

 

 

기본적인 시큐리티 설정

스프링 시큐리티의 기본 설정을 통해 인증 및 인가를 처리할 수 있다. 기본적으로 UserDetailsService를 구현하여 사용자 인증 정보를 관리하도록 하겠다...!

// import 문들은 생략..

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/login", "/public/**").permitAll() // 로그인 페이지와 공용 자원은 모두 접근 가능
                .anyRequest().authenticated() // 그 외 요청은 인증 필요
            .and()
            .formLogin()
                .loginPage("/login") // 커스텀 로그인 페이지
                .defaultSuccessUrl("/dashboard", true) // 로그인 성공 시 대시보드로 이동
                .permitAll()
            .and()
            .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login?logout")
                .permitAll();
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        var user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();
        return new InMemoryUserDetailsManager(user);
    }
}

 

조금 더 부가 설명을 하자면, 다음과 같다.

  • authorizeRequests(): 각 경로에 대한 접근 권한을 설정합니다. 예를 들어, /login 페이지는 누구나 접근할 수 있도록 설정하고, 다른 모든 요청은 인증된 사용자만 접근할 수 있도록 합니다.
  • formLogin(): 스프링 시큐리티가 기본으로 제공하는 로그인 폼을 사용할지 커스텀할지를 설정합니다. 여기서는 /login 페이지를 커스텀 페이지로 지정했습니다.
  • logout(): 로그아웃 처리를 위한 설정입니다. 로그아웃 성공 시 리다이렉트할 경로를 설정할 수 있습니다.
  • InMemoryUserDetailsManager: 간단한 예시로, 메모리 내에서 사용자 정보를 저장하여 인증합니다. 실제 애플리케이션에서는 데이터베이스 연동을 사용합니다.

로그인 페이지 HTML ( Thymeleaf 페이지 생성 )

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
</head>
<body>
    <h2>Login</h2>
    <form th:action="@{/login}" method="post">
        <div>
            <label for="username">Username:</label>
            <input type="text" id="username" name="username"/>
        </div>
        <div>
            <label for="password">Password:</label>
            <input type="password" id="password" name="password"/>
        </div>
        <div>
            <button type="submit">Login</button>
        </div>
    </form>
    <div th:if="${param.error}"> // 로그인 에러 시, error로 넘어오게 처리
        <p>Invalid username or password</p>
    </div>
    <div th:if="${param.logout}"> // 로그아웃 하면, logout으로 넘어오게 처리.
        <p>You have been logged out</p>
    </div>
</body>
</html>

 

로그인 성공 시 처리 할페이지 ( 대시보드 )

// import 문 생략

@Controller
public class DashboardController {

    @GetMapping("/dashboard")
    public String dashboard(Model model, Authentication authentication) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();
        model.addAttribute("username", userDetails.getUsername());
        return "dashboard";
    }
}

 

대시보드 HTML

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Dashboard</title>
</head>
<body>
    <h2>Welcome, <span th:text="${username}">User</span>!</h2>
    <a th:href="@{/logout}">Logout</a>
</body>
</html>

 

CustomUserDetailsService

//import 생략

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("User not found");
        }
        return org.springframework.security.core.userdetails.User
                .withUsername(user.getUsername())
                .password(user.getPassword())
                .roles(user.getRole())
                .build();
    }
}

 

 

장단점

장짐

강력한 보안: 다양한 보안 설정과 인증, 권한 관리를 손쉽게 설정 가능

유연성: 폼 로그인, 세션 기반 인증, OAuth2, JWT 등의 다양한 인증 방식을 위해 사용자 정의 가능

 

단점

복잡성: 세션에 비해서 설정이 복잡하다.

기본 제공 기능의 제한: 간단한 프로젝트에서는 과도할 수 있다.