많은 분들이 Spring boot를 사용하면서 인증/인가 기능을 추가하고자 한다면 Spring Security를 적극 추천합니다.
Spring boot 환경에서 많은 부분을 쉽게 구현하도록 편의성을 제공해 주기 때문이죠!!
그렇다면 Spring 에서 Spring Security의 구조가 어떻게 되어있는지와
기본적인 로그인이 어떻게 동작하는 과정을 살펴봅시다.
구조
Spring Security를 알기 전에 서블릿 기반의 Spring 애플리케이션에서 Filter의 역할을 알아봅시다!
Client의 요청이 다양한 Filter와 Servlet을 포함하는 FilterChain을 거쳐 컨트롤러로 보내집니다.
FilterChain의 Filter에서는 HttpServletReqeust 와 HttpServletResponse 객체를 이용해
특정 상황에서 다음으로 연결된 필터 혹은 서블릿이 호출되는 것을 막으면서 HttpServletResponse를 작성해서 응답해 주거나,
다음으로 연결된 필터 혹은 서블릿을 호출하며 HttpServletReqeust 와 HttpServletResponse 객체를 전달하기 전에 이 두 객체를 수정한 후 전달할 수 있습니다.
Spring Security 는 이 FilterChain의 Filter 들 사이에 FilterProxy를 생성하고, 이를 통해 SecurityFilterChain을 호출되게 합니다.
SecurityFilterChain은 앞서 설명한 FilterChain처럼 각종 필터를 연결해서 구현됩니다.
이렇게 연결된 필터들은 각 엔드포인트로의 요청에 맞게 실행될 수 있습니다.
이 경우 Spring Security 공홈에서 설명하는 장점에 대한 번역입니다.
첫째, Spring Security의 모든 서블릿 지원에 대한 시작점을 제공합니다. 이러한 이유로 Spring Security의 Servlet 지원 문제를 해결하려는 경우 FilterChainProxy에 디버그 지점을 추가하는 것이 좋은 시작점입니다.
둘째, FilterChainProxy는 Spring Security 사용의 핵심이므로 선택 사항으로 간주되지 않는 작업을 수행할 수 있습니다. 예를 들어 메모리 누수를 방지하기 위해 SecurityContext를 지웁니다. 또한 특정 유형의 공격으로부터 애플리케이션을 보호하기 위해 Spring Security의 HttpFirewall을 적용합니다.
또한 SecurityFilterChain을 호출해야 하는 시기를 결정하는 데 더 많은 유연성을 제공합니다. 서블릿 컨테이너에서 필터 인스턴스는 URL만을 기반으로 호출됩니다. 그러나 FilterChainProxy는 RequestMatcher 인터페이스를 사용하여 HttpServletRequest의 모든 항목을 기반으로 호출을 결정할 수 있습니다.


Spring Security의 보안 필터들을 어떤 엔드 포인트에서 실행되게 할지는 Configuration 클래스를 통해 정의할 수 있습니다.
또한, Spring Security에서 제공하는 다양한 필터들을 정의할 수 있습니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(Customizer.withDefaults())
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/","/main").permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}
}
위 코드에서 authorizeHttpRequests() 메서드를 통해 엔드포인트에 따른 보안을 적용할 수 있습니다.
만약, /main 과 / 는 인증될 필요가 없는 URI 고 나머지는 모두 인증이 필요하다고 했을 때 위처럼 설정합니다.
이외에도 내 어플리케이션에 맞는 필터를 적용하거나, 기존의 필터를 수정하여 적용할 수도 있습니다.
또한, 각종 예외 처리를 설정해 줄 필요도 있습니다.
이 부분들은 밑에 있는 공홈 설명을 확인해 주시길 바랍니다!!
로그인
이제 로그인이 동작하는 방법을 살펴봅시다! Form 방식의 로그인을 기준으로 하겠습니다.


1. 위 그림처럼 Client에서 인증이 필요한 요청을 하는 것부터 시작합시다.
2. 해당 요청은 SecurityFilterChain에 존재하는 AuthorizationFilter에 의해서 AccessDeniedException이 발동됩니다.
( FilterSecurityInterceptor 는 사용이 중단되면서 AuthorizationFilter 가 이를 대체합니다. )
3. 사용자가 인증되지 않고 ExceptionTranslationFilter 넘어가면서 해당 필터에서는
Client에게 LoginUrlAuthenticationEntryPoint를 통해 로그인 페이지로 리디렉션 합니다.
4. 사용자가 로그인을 시도하기 위해서 로그인 페이지를 요청합니다.
5. 스프링 애플리케이션에서 로그인 페이지를 응답해 줍니다.
( 만약 스프링을 View 가 분리되도록 구현했다면 4번과 5번을 제외할 수 있습니다. )

로그인을 시도하면 위 그림과 같은 과정이 시작됩니다.
1. Client 가 username과 password를 제출하면서 로그인을 요청하면 UsernamePasswordAuthenticationFilter 는 HttpServletRequest 객체에서 username 과 password를 추출하여 UsernamePasswordAuthenticationToken 객체를 생성합니다.
2. username과 password를 통해 사용자를 인증하기 위해서 생성된 UsernamePasswordAuthenticationToken 객체를 AuthenticationManager에게 전달합니다.
AuthenticationManager는 전달받은 UsernamePasswordAuthenticationToken 객체를 통해 인증된 username과 password 인지 확인하고 성공 혹은 실패 프로세스를 진행합니다.
3. 만약 실패했다면, SecurityContextHolder가 지워지고 RememberMeServices.loginFail() 메서드가 호출됩니다.
그 후 AuthenticationFailureHandler를 호출하여 실패 후의 동작을 실행합니다.
( RememberMeServices의 동작은 java docs를 확인해 주세요! )
( 우리는 AuthenticationFailureHandler를 작성하여 실패 후의 동작을 정의해 줄 수 있습니다. )
4. 만약 성공했다면, SessionAuthenticationStrategy에 새 로그인이 통보됩니다. 그 후 SecurityContextHolder에 인증이 설정되고 RememberMeServices.loginSuccess가 호출되고, ApplicationEventPublisher는 InteractiveAuthencationSuccessEvent를 게시합니다.
그 후 AuthenticationSuccessHandler를 호출하여 성공 후의 동작을 수행합니다.
( AuthenticationSuccessHandler의 앞에서 동작하는 것들은 java docs 등을 확인해 주세요! )
( 우리는 AuthenticationSuccessHandler를 작성하여 성공 후의 동작을 정의해 줄 수 있습니다. )
Spring Security는 기본적으로 로그인 양식이 활성화되어 있지만, 새롭게 정의해줄 수도 있습니다.
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.formLogin(form -> form
.loginPage("/login")
.permitAll()
);
// ...
}
이처럼 로그인이 어떻게 동작하는지도 살펴보았습니다.
추가로 AuthenticationManager가 인증된 UsernamePasswordAuthenticationToken 인지 확인하는 과정을 UserDetailsService.loadUserByUsername()을 호출하여 UserDetails 객체에 조회된 사용자 정보를 담아 진행하는데,
우리가 UserDetailsService를 상속받아 재정의하여 DB 등등에서 인증된 사용자를 조회하고
UserDetails를 상속받은 객체를 통해 조회된 사용자 정보를 담을 수 있습니다.
Reference
https://docs.spring.io/spring-security/reference/servlet/architecture.html
Architecture :: Spring Security
The Security Filters are inserted into the FilterChainProxy with the SecurityFilterChain API. Those filters can be used for a number of different purposes, like authentication, authorization, exploit protection, and more. The filters are executed in a spec
docs.spring.io
https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/index.html
Username/Password Authentication :: Spring Security
Normally, Spring Security builds an AuthenticationManager internally composed of a DaoAuthenticationProvider for username/password authentication. In certain cases, it may still be desired to customize the instance of AuthenticationManager used by Spring S
docs.spring.io
https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html
Form Login :: Spring Security
When the username and password are submitted, the UsernamePasswordAuthenticationFilter authenticates the username and password. The UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter, so the following diagram should look pr
docs.spring.io
처음에 너무 어려울 때 이 분의 유튜브와 노션에 정리된 글을 보고 많은 도움을 받았습니다!!!! (감사합니다 _ _ )
이 분의 강의를 보고 다시 공홈을 천천히 읽어보시면 보다 쉽게 이해할 수 있습니다!
https://substantial-park-a17.notion.site/Docs-002024551c294889863d0c7923590568
개발자 유미 Docs 모음 | Notion
Docs 모음
substantial-park-a17.notion.site
'WEB > Spring' 카테고리의 다른 글
Spring Security 프레임워크로 로그인에 성공하면 JWT 발급하기 (0) | 2024.04.17 |
---|---|
Spring Security 프레임워크로 OAuth2 로그인 (0) | 2024.04.05 |
[Spring JPA] IncorrectResultSizeDataAccessException (0) | 2023.12.05 |
[Spring Boot] API Docs를 자동으로 만들어보자! (1) | 2023.11.25 |
[SpringBoot] SpringBoot에 MongoDB 연결 (0) | 2023.09.12 |