안녕하세요.! 스프링부트 2.7.3버전에서 Spring Security를 사용하는 과정을 여러 차례에 걸쳐 글을 작성하도록 하겠습니다.
이번 포스팅은 Spring Security Config, 스프링 Security formLogin 방식의 절차에 관한 글입니다.
Spring Security는 양이 많아서, 모든 내용을 한 번에 정리하고 이해할 수 없어서, 파트별로 나눠서 작성하며, 명확하게 이해하는 과정이 필요하다고 생각합니다. spring security 5.7버전 이하에서는 WebSecurityConfigurerAdapter 상속받아 Security를 처리했었습니다.
하지만 spring security가 버전업되면 WebSecurityConfigurerAdapter deprecated 되었습니다. Spring Security 과정을 이해하지 못한 상태로 코드를 작성하다 보니 많이 막히게 되었습니다. 이번 기회에 Spring Security를 명확하게 이해하고 넘어가자는 생각에 공부를 진행하고 있고 어려웠던 부분 위주로 내용을 작성해나가겠습니다.!
Spring Security는 Spring 기반의 어플리케이션 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크입니다. Security는 인증과 권한에 대한 부분을 Filter의 흐름에 따라 처리하고 있습니다.
WebSecurityConfigurerAdapter가 Deprecated 되었으니 SecurityFilterChain를 Bean으로 등록해서 사용해야합니다. 그 외 방법은 모두 유사합니다.
- authorizeRequest() (보안 절차를 거치고)
- anyRequest() (어떠한 request라도)
- authenticated() (인증을 받아야 함)
- formLogin() (그 방식은 폼 로그인)
형식으로 설정 클래스가 처리됩니다. 최종적으로 SecurityFilterChain 타입으로 값을 build하여 리턴 해야합니다. 그 외에도 다양한 사용자 정의 보안 설정 적용이 가능합니다.
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.formLogin();
return http.build();
}
기본적으로 사용되는 formLogin 인증 API 구조는 다음과 같습니다.
http.formLogin()
.login("/login")
.defaultSuccessUrl("/home")
.failureUrl("/login")
.usernameParameter("username")
.passwordParameter("password")
.loginProcessingUrl("/login")
.successHandler(loginSuccessHandler())
.failereHandler(loginFailureHander())
- login : 사용자가 정의한 로그인 페이지
- defaultSuccessUrl: 로그인 성공 후 이동하는 페이지
- failureUrl: 로그인 실패 후 이동하는 페이지
- usernameParameter: 폼 태그에 사용되는 아이디 파라미터명
- passwordParameter: 폼 태그에 사용되는 비밀번호 파라미터명
- loginProcessingUrl: 폼 태그에 사용되는 url
- successHandler: 로그인 성공 후 실행되는 handler (defaultSuccessUrl가 페이지 이동의 역할만 수행한다면, 핸들러에서는 다양한 로직을 구성할 수 있습니다)
- failereHandler: 로그인 실패 후 실행되는 handler
.successHandler((request, response, authentication) -> {
log.info("authentication name = {}", authentication.getName())
response.sendRedirect("/");
})
.failureHandler((request, response, exception) -> {
log.info("exception.getMessage() = {}", exception.getMessage());
response.sendRedirect("/login");
})
저는 개인적으로 핸들러의 역할에 대해서 이해가 부족해서, 핸들러와 컨트롤러의 처리 과정이 많이 헷갈렸습니다. 다양한 구글링 정보를 토대로 제가 이해한 바는 이렇습니다.
핸들러는 스프링MVC의 웹 요청을 처리하는 객체를 범용적으로 핸들러라고 표현하고 있습니다. 컨트롤러도 범용적인 표현으로 핸들러라고 할 수 있습니다.
하지만, Spring Security는 dispatcherServlet 이전에 필터링하는 역할을 수행하므로, 여기서 지칭하는 핸들러는 Security에 내장된 혹은 사용자가 정의한 핸들러입니다.
저는 위에 successHandler를 람다식으로 해당 폼 로그인을 성공하면 인증객체의 이름으로 로그를 찍고, "/"으로 리다이렉트하는 핸들러를 작성한 것입니다. 만약 실패할 경우, 필터가 fauilerHandler를 찾아 해당 람다식 핸들러를 실행시킵니다.
폼 로그인은 다음과 같은 절차로 이루어집니다.
- Http Request 요청이 오면 UsernamePasswordAuthenticationTokenFilter가 작동합니다.
- UsernamePasswordAuthenticationTokenFilter는 request가 AuthPathRequestMather("/사용자설정 로그인 url") 이 실행되는데, 우리가 설정한 로그인 url과 같은지 처리하고 같다면 Authentication 객체를 생성합니다.
- 해당 객체는 AuthenticationManager에게 전달되고, AuthenticationManager 인터페이스는 이를 구현하고 있는 AuthenticationProvider 구현체에 해당 요청에 맞는 구현체를 선정하여 인증 절차를 거칩니다.
- 인증이 성공한다면, 인증된 Authentication 객체를 filter에게 전달하게 되고, SecurityContextHolder에 저장하고, 성공 핸들러가 실행됩니다. 만약 실패한다면, AuthenticationException 예외가 처리됩니다.
잘못된 부분이 있다면, 댓글 부탁드립니다.!
오늘도 즐거운 하루 보내세요~! 감사합니다.!!!
참고 자료:
정수원님 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security
망나니개발자님 tistory - https://mangkyu.tistory.com/76