240626
오늘의 학습
● SpringSecurity 자습
Spring Security를 이용하며 생긴 문제와 해결
Security 설정에 requestMatchers().permitAll().anyRequest().authenticated() 메서드를 이용하면 인가 권한이 없이 넘어갈 수 있다고 들어 개인적으로 해석을하여 위의 빌더 방식의 메서드를 호출하면 SecurityFilterChain에 들어있는 Filter들을 모두 건너뛰어 바로 Controller로 넘어가는 줄 알았지만, FilterChain 내부에 커스텀한 JwtAuthorizationFilter에 토큰 검증 메서드를 수행한다는 사실을 알게되었다. 결국 SecurityFilterChain의 Filter를 탄다는 사실을 알게되었고, 그럼 위의 인가 권한 없이 넘어갈 수 있는 메서드는 어떠한 동작을 하는가에 대해서 의문점이 들었고 이유를 찾고자 테스트를 진행했고, 결과적으로는 SecurityFilter에 대해 공부를 하게 되었다.
HttpSecurity => SecurityFilterChain에 해당하는 Filter들을 제어해주는 역할
=> 필터 추가 및 세션 사용 여부 등등 필터 자체의 제어 하는 역할을 한다고 생각하면 된다.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf((csrf) -> csrf.disable());
http.sessionManagement((sessionManagement) ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
http.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.requestMatchers(
"/api/users/signup", // 회원가입[POST]
"/api/users/login", // 로그인[POST]
"/api/users/refresh", // access 토큰 재발급[POST]
"/api/users/logout" // logout[POST]
).permitAll()
.requestMatchers(
HttpMethod.GET,
"/api/boards", // 게시글 전체 조회[GET]
"/api/boards/*" // 게시글 선택 조회[GET]
).permitAll()
.requestMatchers(
"/api/boards/*/comments" // 댓글 전체 조회[GET]
).permitAll()
.anyRequest().authenticated()
);
http.addFilterBefore(jwtAuthorizationFilter(), JwtAuthenticationFilter.class);
http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
// 여기서 결국 어나니머스SecurityContext 또는 유저 정보가 들어있는 SecurityContext 둘 중 하나만 들어오게 된다.
// .anyRequest().authenticated()
// 이 메서드에서 유저정보가 들어있는 SecurityContext일 경우 통과를 시켜주고
// 어나니머스SecurityContext일 경우 예외를 발생하게 된다.
// 해당 예외는 FilterSecurityInterceptor에서
// AccessDeniedException또는 AuthenticationException예외가 발생하게 되고
// 해당 예외들은 ExceptionTranslationFilter에서 처리가 된다.
1. Controller에서 api 테스트
가장 먼저 이유를 알고자 requestMatchers()에 넣지 않은 api 하나를 만들어 테스트를 진행하게 되었다.
결과는 403 Forbidden이 나왔고 로그에는 아무것도 찍히지 않았다.
여기서 DEBUG 로그를 찍으면 자세한 로그가 나온다는 사실을 알게 되었고
application.properties에 로그레벨을 DEBUG로 두고 찍게되었다.
logging.level.org.springframework.security=DEBUG
결과는 Set SecurityContextHolder to anonymous SecurityContext 로그 메시지가 찍힌 것을 확인할 수 있었고
해석하면, "SecurityContextHolder를 익명 SecurityContext로 설정합니다." 라는 뜻이다.
익명 SecurityContext??가 뭐지? 싶어서 해당 로그가 찍힌 클래스인 AnonymousAuthenticationFilter클래스에
가서 코드를 분석하게 되었다.
2. AnonymousAuthenticationFilter
해당 필터 클래스 내부를 확인해보면 defaultWithAnonymous() 메서드가 존재하는것을 확인할 수가 있고 해당 메서드는 필터체인을 통해 현재 필터 클래스로 넘어왔을 경우 해당 메서드가 필수적으로 수행이 된다.
defaultWithAnonymous() 메서드를 분석해보자.
해당 메서드의 인자값으로는 화면에서 넘겨준 request 정보와 SecurityContext를 받고있다 해당 SecurityContext에는 앞에서 SecurityContext에 넣어준 유저의 정보를 담고있는 것을 받고있다.
72번 라인의 로직은 context 내부의 유저정보가 담겨있는 Autentication객체를 변수에 할당하고 있는 로직이다.
이후, if문을 수행하게 된다.
if문에서의 조건은 Autentication객체에 null인 경우와 아닌 경우를 조건으로 두고 있으며, null인 경우는 Autentication객체가 비어있다는 의미이며, 결국 SecurityContext가 비어있을 경우인 것이다. 여기서 SecurityContext가 비어있는 경우로는 로그인을 하지 않았다는 증거이며, 토큰이 없어 검증이 안되었다는 의미이다.
로그인이 안되어 비어있는 경우에는 Autentication객체를 하나 만들어 임의로 익명의 유저를 만들어 넣게 된다.
만들어진 Autentication객체를 다시 SecurityContext에 넣어 anonymous객체로 만들어 return을 하게 된다.
return을 하게 되면 해당 클래스의 doFilter 메서드로 return을 하게 되며 doFilter를 통해 다음 필터로 넘어가게 된다는 사실을 알게 되었다.
결론은, SecurityFilter에 대한 구조를 알아야지 이해를 제대로 할 수 있을 것 같다고 생각을 하여 SecurityFilter구조에 대해 공부를 하게되었다.
3. SecurityFilter의 구조를 파악
Spring Security를 이용을 하게 되면 내부적으로 위의 사진과같은 내부적으로 필터가 동작하게 된다는 사실을 알게되었고,
SecurityFilterChain은 필터들을 list로 보관을 하고 있으며, 해당 필터들을 순서대로 동작을 시킨다는 사실을 알게되었다.
사진을 자세히 보면 위에서 까본 클래스인 AnonymousAuthenticationFilter 클래스가 상당히 아랫쪽에 위치한다는 것을 볼 수 있다. 즉, 내가 만든 커스텀 Filter인 인증 필터가 먼저 동작된다는 것을 알 수 있었다.
그리고 여기서 마지막에 ExceptionTransiationFilter와 FilterSecurityInterceptor를 확인할 수 있고 이 둘의 역할은
먼저 인증과 인가에 대한 예외가 발생하면 FilterSecurityInterceptor에서
AccessDeniedException 또는 AuthenticationException의 예외가 발생하게 되고 해당 예외는 ExceptionTranslationFilter가 처리를 해주게 되며 결국 위에서 테스트한 내용에 403 Forbidden과 같이 이러한 예외를 처리하여 적절한 HTTP 응답을 반환하는 역할을 하게 된다.
결론
결과적으로는
참고
403 Forbidden : 인증된 사용자가 필요한 권한이 부족한 경우에 발생하는 예외
401 Unauthorized : 인증되지 않은 사용자가 보호된 리소스에 접근하려고 할 때 발생하는 예외
회고
비록 오늘 많은 삽질을 하였지만 기본적인 Security의 흐름을 알 수 있었고, Filter에서 예외처리 또한 궁금했는데 궁금증이 해결이 되었다. 다음에는 프로젝트에 Filter에서 커스텀 예외를 처리를 해봐야겠다고 생각을 하게 되었다.
하루만에 이 정도 이해한거면 그래도 나름 순방한거 같다..
'내일배움캠프 Spring 5기' 카테고리의 다른 글
내일배움캠프 51일차 TIL - AWS(1) (0) | 2024.06.28 |
---|---|
내일배움캠프 50일차 TIL - JDBC (0) | 2024.06.27 |
내일배움캠프 48일차 TIL - 아웃소싱 프로젝트[KPT회고] (0) | 2024.06.25 |
내일배움캠프 42일차 TIL - 톰캣, 서블릿 (0) | 2024.06.17 |
내일배움캠프 41일차 TIL - Spring AOP (0) | 2024.06.14 |