inblog logo
|
LifeLog, DevLog
    Spring

    JWT + Filter 실습해보기

    Filter 단계 에서 검증 및 예외처리하기
    KYJTHEYJ's avatar
    KYJTHEYJ
    Jan 22, 2026
    JWT + Filter 실습해보기
    Contents
    Filter에서 처리하자Servlet APIRequestResponseFilter 에서 JWT 검증

    Filter에서 처리하자

    이전에 JWT의 생성, 검증 방법을 알아보았다

    검증을 매번하는 건 당연히 무리다
    그러니 우리는 Filter 에서 체크하여
    코드의 중복도 줄이고 컨트롤러 접근 자체를 막아보자

    다만 필터는 스프링 MVC 접근 전이라
    일반적으로는 JSON 에러 처리를 직접 하던
    조용히 false 처리하여 처리하던 (→ 스프링 시큐리티와 함께 엔드포인트에서 처리)
    따로 설정하여 스프링의 예외처리단에서 예외처리를 하는

    크게 4가지 방법이 있고, 우선 지금은 직접 JSON 으로 예외처리를 하고
    나머지는 추후 이 포스팅을 수정하며 알아보도록 할 것이다

    Servlet API

    서블릿 API에서도 필요한 것들은 우선 정리했다

    Request

    getRequestURI, URL() + 그외 *

    URI, URL 정보를 얻는다

    // URL, URI 정보를 얻기
    String uri = request.getRequestURI();           
    String url = request.getRequestURL().toString();
    
    // 메서드와 쿼리스트링 정보를 얻기
    String method = request.getMethod();           
    String queryString = request.getQueryString();

    getParameter(), getParameterMap() *

    파라미터의 정보를 가져온다

    String id = request.getParameter("id");
    
    // 요청 파라미터 전체를 가져옴
    Map<String, String[]> params = request.getParameterMap();

    Attribute Setter *

    Filter에서 데이터 전달에 사용하기 위해 속성을 설정한다

    request.setAttribute("userId", 123L);
    Long userId = (Long) request.getAttribute("userId");

    Response

    setContentType(), setCharacterEncoding() *

    응답 Content-Type을 설정한다

    response.setContentType("application/json");  // JSON
    response.setContentType("text/html");         // HTML
    response.setContentType("text/plain");        // 일반 텍스트
    
    // 인코딩은 따로 setCharacterEncoding() 으로 지정하거나
    // 타입 지정시에 타입; charset=UTF-8; 같이 지정해줘도 된다

    getWriter() *

    문자열 응답에 사용한다 (JSON, HTML, XML 등)

    PrintWriter writer = response.getWriter();
    writer.write("Hello");
    writer.write("{\"message\": \"success\"}");
    
    // DTO를 Json 형태의 변환이 필요하면
    //ObjectMapper 를 활용해서 writeValueAsString 하는 등의 방법이 더 좋긴하다

    getOutputStream() *

    바이너리 응답에 사용한다 (파일, 이미지 등)

    ServletOutputStream outputStream = response.getOutputStream();
    outputStream.write(bytes);

    💡

    응답의 본문은 둘 중 하나만 사용해야한다
    둘다 사용하면 IllegalStateException 발생

    sendRedirect(URI) *

    // 오류가 발생하여 로그인 페이지로 되돌릴 경우
    private void redirectToLogin(HttpServletResponse response) throws IOException {
        response.sendRedirect("/login");
        // 또는
        response.setStatus(HttpStatus.FOUND.value());  // 302
        response.setHeader("Location", "/login");
    }

    Filter 에서 JWT 검증

    @Slf4j
    @Order(3)
    @Component
    @RequiredArgsConstructor
    public class JwtAuthFilter extends OncePerRequestFilter {
        private final JwtUtil jwtUtil;
        private final ObjectMapper objectMapper;
        private final AdminDetailService adminDetailService;
    
        // 인증이 필요 없는 URL 패턴
        private static final List<String> PERMIT_ALL_PATH_ANT_STYLE = List.of(
                "/api/auth/**",
                "/api/admins/signUp",
                "/api/admins/login"
        );
    
        // URL 패턴 검증용 (Ant 스타일)
        private final AntPathMatcher antPathMatcher = new AntPathMatcher();
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            // 요청
            log.info("JwtAuthFilter IN");
    
            // 올바른 토큰이 있는지?
            // Authorization 토큰
            String authorization = request.getHeader("Authorization");
    
            if(authorization == null || !authorization.startsWith("Bearer ")) {
                MessageResponse errorResponse = MessageResponse.fail(HttpStatus.UNAUTHORIZED.name(),  "인증 정보가 없습니다");
                String json = objectMapper.writeValueAsString(errorResponse);
    
                response.setContentType("application/json; charset=UTF-8");
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                response.getWriter().write(json);
                return;
            }
    
            // Bearer 검증 잘라내기
            String token = authorization.substring("Bearer ".length());
    
            if(!jwtUtil.validateToken(token)) {
                MessageResponse errorResponse = MessageResponse.fail(HttpStatus.UNAUTHORIZED.name(),  "인증 정보에 대한 확인이 필요합니다");
                String json = objectMapper.writeValueAsString(errorResponse);
    
                response.setContentType("application/json; charset=UTF-8");
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                response.getWriter().write(json);
                return;
            } else {
                // 토큰 검증 완료 되었으니 정보를 담아서 활용
                // 토큰에서 이메일 추출
                String email = jwtUtil.getEmail(token);
    
                // UserDetailService로 정보 조회
                UserDetails adminDetail = adminDetailService.loadUserByUsername(email);
    
                // 인증 객체 생성
                UsernamePasswordAuthenticationToken authentication
                        = new UsernamePasswordAuthenticationToken(
                                adminDetail // principal (누구인지)
                                , null // credentials
                                , adminDetail.getAuthorities()); // 권한 정보
    
                // SecurityContext에 인증 객체 저장
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
    
            filterChain.doFilter(request, response);
    
            // 응답
            log.info("Test Filter2 OUT");
        }
    
        // URI가 토큰 체크를 해야하는 URI 인지?
        @Override
        protected boolean shouldNotFilter(@NonNull HttpServletRequest request) {
            log.info("JwtFilter IN - request URI : {}",  request.getRequestURI());
    
            return PERMIT_ALL_PATH_ANT_STYLE.stream().anyMatch(antStyleUri -> antPathMatcher.match(antStyleUri, request.getRequestURI()));
        }
    }

    커스텀으로 처리한 DTO가 있어서 응답 양식을 맞춰주었다 (코드 중복은 우선 생략)

    이제 Authorization 헤더에 토큰을 담아보면.. 인증이 통과한다
    그리고 앞으로 요청에는 로그인한 계정의 이메일이 담길 것이다

    이를 기반으로 쿠키에 저장한 토큰 값을 꺼내 검증하는 것을 추가하면
    RefreshToken 처리와 실제 접근 AccessToken 으로 구분하여 보안을 올릴 수 있다

    Share article
    Contents
    Filter에서 처리하자Servlet APIRequestResponseFilter 에서 JWT 검증

    LifeLog, DevLog - https://github.com/KYJTHEYJ

    RSS·Powered by Inblog