문제 상황
•
Main 클라우드 서버(EC2 혹은 oracle ) 에 배포하였을 때,
Swagger GET 요청 외에 다른 요청이 안되는 현상
•
POST, PATCH, DELETE, GET 을 제외한 모든 요청이 403 ( Not Allowed) 응답
•
POSTMAN 이나 curl 은 되는 현상
원인
•
추정 원인
◦
Spring Security CORS 설정 문제
•
실제 원인
◦
Spring Security CORS 설정 문제
최종 해결
@Configuration
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig {
private final AuthenticationManager jwtAuthenticationManager;
private final AccessDeniedHandler jwtAccessDeniedHandler;
private final AuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAuthenticationConverter jwtAuthenticationConverter;
private final JwtDecoder jwtDecoder;
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
// @formatter:off
return httpSecurity
.cors()
.configurationSource(corsConfigurationSource())
.and()
.csrf().disable()
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// TODO: 나중에 front주소로 바꿔야함
configuration.addAllowedOrigin("http://localhost:3000");
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Java
복사
•
해결방법
•
addAllowedOrigin 에, Swagger 요청주소가 없었다.
@Configuration
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig {
...
@Value("${swagger.base-url}")
private String baseUrl;
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("localhost:3000", baseUrl));
...
}
Java
복사
•
configuration.setAllowedOrigins(List.of("localhost:3000", baseUrl));
•
프론트요청과, baseUrl (Swagger url) 을 CORS Allow 에 추가하여 해결하였다.
참고자료
EC2 로그와 로컬 로그를 비교했다.
•
무시된 POST 요청 로그
[nio-8080-exec-9] o.s.security.web.FilterChainProxy : Securing POST /v1/images/presigned-url
[nio-8080-exec-9] o.j.e.auth.MyCustomLoggingFilter : MyCustomLoggingFilter.doFilterInternal() is invoked.
[nio-8080-exec-9] o.j.e.auth.MyCustomLoggingFilter : request: /v1/images/presigned-url
[nio-8080-exec-9] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
[nio-8080-exec-9] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
Java
복사
•
정상적인 GET 요청 로그
[nio-8080-exec-7] o.s.security.web.FilterChainProxy : Securing GET /v1/bookmarks?page=0&size=10
[nio-8080-exec-7] o.j.e.auth.MyCustomLoggingFilter : MyCustomLoggingFilter.doFilterInternal() is invoked.
[nio-8080-exec-7] o.j.e.auth.MyCustomLoggingFilter : request: /v1/bookmarks
[nio-8080-exec-7] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
[nio-8080-exec-7] .o.s.r.w.BearerTokenAuthenticationFilter : Set SecurityContextHolder to JwtAuthenticationToken [Principal=org.springframework.security.oauth2.jwt.Jwt@decdbdc6, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER]]
[nio-8080-exec-7] o.j.e.a.UserSessionAuthenticationFilter : UserSessionAuthenticationFilter.doFilterInternal() is invoked.
[nio-8080-exec-7] o.j.e.a.UserSessionAuthenticationFilter : request: org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@7e233ed
[nio-8080-exec-7] o.j.e.a.UserSessionAuthenticationFilter : request Referer: https://api.exchange-diary.com/swagger-ui/index.html
[nio-8080-exec-7] o.j.e.a.UserSessionAuthenticationFilter : convert token: principalUserSessionDto(userId=1, roles=["USER"]), authorities [ROLE_USER]
[nio-8080-exec-7] o.s.s.w.a.i.FilterSecurityInterceptor : Authorized filter invocation [GET /v1/bookmarks?page=0&size=10] with attributes [permitAll]
[nio-8080-exec-7] o.s.security.web.FilterChainProxy : Secured GET /v1/bookmarks?page=0&size=10
[nio-8080-exec-7] o.j.e.MyCustomSecurityFilter : Not security filter
[nio-8080-exec-7] o.s.s.a.i.a.MethodSecurityInterceptor : Authorized ReflectiveMethodInvocation: public org.johoeunsae.exchangediary.dto.NotePreviewPaginationDto org.johoeunsae.exchangediary.bookmark.controller.BookmarkController.getBookmarkList(org.johoeunsae.exchangediary.auth.UserSessionDto,int,int); target is of class [org.johoeunsae.exchangediary.bookmark.controller.BookmarkController] with attributes [[authorize: 'isAuthenticated()', filter: 'null', filterTarget: 'null']]
[nio-8080-exec-7] o.j.e.b.controller.BookmarkController : [userId : 1] getBookmarkList
[nio-8080-exec-7] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
Java
복사
•
Spring Security 흐름
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.cors()
.configurationSource(corsConfigurationSource())
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().permitAll()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.oauth2ResourceServer()
.bearerTokenResolver(new DefaultBearerTokenResolver())
.jwt()
.jwtAuthenticationConverter(jwtAuthenticationConverter)
.decoder(jwtDecoder)
.authenticationManager(jwtAuthenticationManager)
.and()
.accessDeniedHandler(jwtAccessDeniedHandler)
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.formLogin().disable()
.addFilterAfter(
new UserSessionAuthenticationFilter(), BearerTokenAuthenticationFilter.class
)
.addFilterBefore(
new MyCustomLoggingFilter(), WebAsyncManagerIntegrationFilter.class
)
.build();
}
Java
복사
◦
.addFilterAfter() ⇒ SpringSecurity
◦
.addFilterBefore() ⇒ SpringSecurity 로직 필터 이전에 실행된다.
@Log4j2
public class MyCustomLoggingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
log.info("MyCustomLoggingFilter.doFilterInternal() is invoked.");
log.info("request: " + request.getRequestURI());
doFilter(request, response, filterChain);
}
}
Java
복사