이번 프로젝트에서 보안을 담당하며 Spring Security와 JWT를 연동하여 인증 시스템을 구축하였다. 단순히 코드를 복사하는 것이 아니라, 전체적인 데이터 흐름(Request ~ Response)을 파악하기 위해 구조도를 정리해 보았다.
Spring Security는 기본적으로 필터(Filter) 기반으로 동작함. 사용자 요청이 Controller에 도달하기 전, Servlet Container와 DispatcherServlet 사이에서 보안 검사를 수행함.
JwtAuthenticationFilter (커스텀 필터):
SecurityContextHolder:
AuthorizationFilter (권한 검사):
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final ObjectMapper objectMapper;
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
return path.equals("/admin/login") || path.equals("/admin/signup");
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
// 토큰 없으면 401 (인증 에러 발생)
// 공통된 에러 응답 메세지 출력을 위해 별도로 구현
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
writeUnauthorized(request, response, AuthErrorCode.TOKEN_MISSING);
return;
}
String token = authHeader.substring(7);
try {
// 토큰 서명검증, 유효기간 검증 성공 시 try문 실행, 실패시 catch문에서 예외 처리
jwtUtil.validateOrThrow(token);
// 검증이 끝난 토큰으로 authentication 새로 생성
// JWT - 신분증, Authentication - 출입증, 매번 요청시 출입증 발급
Authentication authentication = jwtUtil.getAuthenticationFromToken(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
} catch (UnauthorizedException e) {
writeUnauthorized(request, response, e.getAuthErrorCode());
} catch (Exception e) {
writeUnauthorized(request, response, AuthErrorCode.TOKEN_INVALID);
}
}
