* OncePerRequestFilter 를 상속한 커스텀 필터는 대체로 SecurityFilterChain에서 맨 앞쪽에 배치됨.
* 혹은 SecurityFilterChain에서 .addFilterBefore()나 .addFilterAfter 등으로 위치 지정 가능
* filterChain.doFilter(request, response)를 호출해야 다음 필터 또는 dispatcherServlet으로 전달됨
* JWT filter에서 예외가 발생하면 catch 로 잡아서 doFilter를 하여도 전의 corsfilter에서 달았던 cors 관련 헤더가 사라지는 현상 발생함
1. Request
2. CorsFilter (CORS 헤더 준비후에 filterChain.doFilter)
3. JwtFilter (JWT 토큰 검증하여 SecurityContextHolder세팅후 filterChain.doFilter)
4. UsernamePasswordAuthenticationFilter(앞에서 인증실패하여 SecurityContextHolder 없는 경우 username과 password로 세팅)
5. AuthenticationManager 에서 username 이 빈 문자열이면 BadCredentialsException 발생, 던짐
6. ExceptionTranslationFilter에서 AuthenticationException으로 잡힘
7. AuthenticationEntryPoint를 통해 처리되는 과정중 sendError 호출 -> 컨테이너가 응답을 새로 쓰는 과정에서 사라짐
sendError를 호출하게 되면 톰캣이 응답을 작성하기 때문에 Spring Security가 세팅해둔 cors 헤더나 json본문이 날아감.
@Component
@RequiredArgsConstructor
@Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final Environment env;
@Override
protected void doFilterInternal(final HttpServletRequest request,
@NotNull final HttpServletResponse response,
@NotNull final FilterChain filterChain)
throws ServletException, IOException {
String accessToken = request.getHeader("Authorization");
if (StringUtils.isNotEmpty(accessToken)) {
try {
Claims claim = jwtService.getClaims(accessToken);
if (claim != null) {
//인증된 사용자로 설정
SecurityContextHolder.getContext().setAuthentication(jwtService.getAuthenticationFromClaim(claim));
} else {
throw new IllegalArgumentException("Claims is null");
}
} catch (ExpiredJwtException e) {
log.debug("TOKEN IS EXPIRED");
setErrorCodeAndMessage(response, EXPIRED_TOKEN);
return;
} catch (Exception e) {
log.debug("FAILED TO GET TOKEN INFORMATION.");
setErrorCodeAndMessage(response, INVALID_TOKEN);
return;
}
}
filterChain.doFilter(request, response);
}
//token 관련 에러코드와 메세지를 응답에 세팅
private void setErrorCodeAndMessage(HttpServletResponse response, ErrorCode errorCode) {
try {
TBResponse tbResponse = TBResponse.failed(errorCode);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(tbResponse);
if (!Arrays.asList(env.getActiveProfiles()).contains("prod")) {
response.setContentType("application/json;charset=UTF-8");
response.addHeader("Access-Control-Allow-Origin", "*");
}
response.addHeader("Access-Control-Allow-Credentials", "true");
response.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
response.addHeader("Access-Control-Allow-Headers",
"Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers, Authorization");
response.addHeader("Access-Control-Expose-Headers",
"Access-Control-Allow-Origin, Access-Control-Allow-Credentials, Content-Disposition");
response.addIntHeader("Access-Control-Max-Age", 10);
response.getWriter().write(json);
} catch (Exception e) {
log.warn("");
}
}
}
'코딩 관련 > Spring 관련' 카테고리의 다른 글
| Springboot에서 STOMP 구현하기 / springboot stomp 예시 / spring boot websocket stomp / STOMP 메세지 테스트 예시 (0) | 2024.02.17 |
|---|---|
| [Springboot][JAVA] springboot로 websocket 구현하기 (0) | 2024.02.13 |
| @Autowired is null (0) | 2023.02.16 |
| Springboot JPA 연동 / postgreSQL JPA 연동 (0) | 2023.02.09 |
| springboot H2 연동하기 / JPA 사용하기 (0) | 2023.01.27 |

