본문 바로가기
  • Coding & Book
Back-End/Springboot

Spring Security에서 JWT 인증 필터 구현하기

by 루이3 2025. 3. 18.

구현하게 된 이유

 

프로젝트를 진행하면서 인증 방식을 고민하다 보니 JWT를 보게 되었습니다.

처음에는 세션 기반 인증 방식을 고려했지만, 다양하게 인증 방식을 사용해보고 싶었는데요

 

물론 이전 프로젝트에서 JWT 인증을 적용했던 경험이 있었는데

당시에는 Refresh Token을 고려하지 않고 Access Token만을 사용하여 구현했었습니다.

처음에는 큰 문제가 없다고 생각했지만

시간이 지나면서 Access Token이 만료될 때마다 사용자가 다시 로그인해야 하는 불편함이 발생했었습니다.

 

이를 해결하기 위해 급하게 Refresh Token을 추가하려고 했지만

기존 구조와 많은 충돌이 생기고 예상치 못한 오류들이 계속 발생하여

처음부터 Refresh Token을 고려하여 JWT로 구현해보고 싶었던 것 같습니다.

 

Spring Security에서 JWT 인증 필터 구현하기

Spring Security에서 JWT를 활용한 인증 방식을 적용하기 위해서는

로그인 요청을 처리하는 필터와 이후 요청에서 토큰을 검증하는 필터를 구현해야 합니다.

기본적으로 Spring Security는 세션을 사용한 인증 방식을 제공하지만,

세션을 유지하지 않고 JWT로도 인증을 관리할 수 있습니다.

 

 

JWT 인증 방식의 흐름

JWT 기반 인증은 로그인 후 서버에서 클라이언트에게 Access Token과 Refresh Token을 발급하고

이후 클라이언트는 API 요청 시 Access Token을 포함하여 인증을 진행하는 방식입니다.

기본적인 흐름은 다음과 같습니다.

 

  1. 사용자가 아이디와 비밀번호를 입력하고 로그인 요청을 보냅니다.
  2. 서버는 사용자의 정보를 검증한 후, Access Token과 Refresh Token을 발급합니다.
  3. 클라이언트는 API 요청 시 Access Token을 포함하여 전송합니다.
  4. 서버는 Access Token을 검증하여 인증된 요청인지 확인합니다.
  5. Access Token이 만료되면 Refresh Token을 이용하여 새로운 Access Token을 발급받을 수 있습니다.

Spring Security에서는 이러한 흐름을 처리하기 위해

UsernamePasswordAuthenticationFilter와 OncePerRequestFilter를 사용하여 로그인과 토큰 검증을 구현할 수 있는 것 같습니다.

 

로그인 필터 구현 (LoginFilter)

먼저 로그인 요청을 처리하고 JWT를 발급하는 필터를 구현하겠습니다.

UsernamePasswordAuthenticationFilter를 상속받아 사용자의 로그인 요청을 처리하고

인증이 완료되면 Access Token과 Refresh Token을 생성하여 응답에 포함시키면 됩니다.

LoginFilter 코드

public class LoginFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;
    private final JWTUtil jwtUtil;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public LoginFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil) {
        this.authenticationManager = authenticationManager;
        this.jwtUtil = jwtUtil;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) 
            throws AuthenticationException {
        try {
            LoginRequest loginRequest = objectMapper.readValue(request.getInputStream(), LoginRequest.class);
            UsernamePasswordAuthenticationToken authToken = 
                    new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
            return authenticationManager.authenticate(authToken);
        } catch (IOException e) {
            throw new AuthenticationServiceException("잘못된 로그인 요청 입니다");
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, 
                                            FilterChain chain, Authentication authentication) throws IOException {
        String username = authentication.getName();
        String accessToken = jwtUtil.createJwt("access", username, 600000L);
        String refreshToken = jwtUtil.createJwt("refresh", username, 86400000L);

        response.setHeader("Authorization", "Bearer " + accessToken);
        response.getWriter().write(objectMapper.writeValueAsString(Map.of("accessToken", accessToken)));
    }
}

순서

  1. 로그인 요청을 JSON 형식으로 받아 LoginRequest 객체로 변환
  2. UsernamePasswordAuthenticationToken을 생성하여 Spring Security의 AuthenticationManager를 통해 인증을 수행
  3. 인증이 성공하면 successfulAuthentication을 실행하여 Access Token과 Refresh Token을 생성한 후 응답 헤더에 추가

 

JWT 검증 필터 구현 (JWTFilter)

다음은 Access Token을 검증하여 인증 여부를 판단하는 필터를 구현해 보겠습니다.

OncePerRequestFilter를 상속받아 요청마다 실행되도록 설정하고

유효한 Access Token이 포함된 요청만 SecurityContext에 인증 정보를 저장하는 것입니다.

 

JWTFilter 코드

public class JWTFilter extends OncePerRequestFilter {

    private final JWTUtil jwtUtil;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public JWTFilter(JWTUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String accessToken = request.getHeader("access");

        if (accessToken != null) {
            try {
                jwtUtil.isExpired(accessToken);

                String category = jwtUtil.getCategory(accessToken);
                if (!"access".equals(category)) {
                    setErrorResponse(response, AuthErrorCode.NOT_WOOHAENGSHI_TOKEN);
                    return;
                }

                String username = jwtUtil.getUsername(accessToken);
                String roleString = jwtUtil.getRole(accessToken);
                Role role = Role.valueOf(roleString);

                UserEntity userEntity = new UserEntity();
                userEntity.setUsername(username);
                userEntity.setRole(role);

                CustomUserDetails customUserDetails = new CustomUserDetails(userEntity);
                UsernamePasswordAuthenticationToken authToken =
                        new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());

                SecurityContextHolder.getContext().setAuthentication(authToken);
            } catch (ExpiredJwtException e) {
                setErrorResponse(response, AuthErrorCode.EXPIRED_TOKEN);
                return;
            } catch (SignatureException e) {
                setErrorResponse(response, AuthErrorCode.FAILED_SIGNATURE_TOKEN);
                return;
            } catch (JwtException | IllegalArgumentException e) {
                setErrorResponse(response, AuthErrorCode.INCORRECTLY_CONSTRUCTED_TOKEN);
                return;
            }
        }
        filterChain.doFilter(request, response);
    }
}

순서

  1. 요청 헤더에서 Access Token을 추출하여 유효성을 검사
  2. Access Token이 access 유형인지 확인하고, 유효하면 SecurityContext에 인증 정보를 설정
  3. 만료되거나 서명이 위조된 토큰일 경우 인증 실패 응답을 반환

 

 

마무리

JWT 기반 인증을 구현하기 위해

UsernamePasswordAuthenticationFilter와 OncePerRequestFilter를 아래와 같이 활용할 수 있습니다.

  • LoginFilter를 통해 사용자의 로그인 요청을 처리하고 Access Token을 발급
  • JWTFilter를 통해 모든 요청에서 Access Token을 검증하고 인증 정보를 SecurityContext에 설정

이러한 방식으로 JWT를 활용하면 세션을 유지하지 않고도 인증 관리를 할 수 있으며

RESTful API 환경에서 효율적으로 보안 기능을 적용할 수 있을 것 같습니다.

 

추가적인 내용은 Spring Security 공식 문서를 참고하시면 좋을 것 같습니다. ^^

 

 

 


참고자료

https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html

 

Form Login :: Spring Security

When the username and password are submitted, the UsernamePasswordAuthenticationFilter creates a UsernamePasswordAuthenticationToken which is a type of Authentication, by extracting the username and password from the HttpServletRequest instance. The Userna

docs.spring.io

https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html

 

OAuth 2.0 Resource Server JWT :: Spring Security

Most Resource Server support is collected into spring-security-oauth2-resource-server. However, the support for decoding and verifying JWTs is in spring-security-oauth2-jose, meaning that both are necessary in order to have a working resource server that s

docs.spring.io

https://www.youtube.com/@xxxjjhhh

 

개발자 유미

백엔드 개발자 유미 - 실습 위주 진행 (개념적인 부분은 공식 Docs 참조 및 개인 학습 바랍니다!) - 간혹 댓글 알림이 안오는 경우가 있습니다.

www.youtube.com

 

 

틀린 부분 있을 시 알려주시면 빠르게 수정하겠습니다~