[골든리포트!] 7) JWT토큰 인증과정을 Spring Security 필터체인에 태워보기

2026. 2. 18. 01:05·개인 프로젝트/[2026] 골든리포트!

저번에 인증과정에서 SecurityFilterChain을 어느정도 알아보았다. 이번에는 저번에 만들어본 JWTAuthorizationFilter를 수정해보도록한다.

 

https://dev-dx2d2y-log.tistory.com/216

 

[골든리포트!] 6) 필터체인에서 JWT필터 인증 처리하기

몇 가지 수정사항이 생겼다.GDG 플젝트랙에서는 모든 경로를 permitAll() 해주고 인증이 필요한 요청에 대해 헤더에 있는 JWT 토큰을 검사하는 방식으로 구현했다. 물론 이 방식은 딱히 좋은 편이 아

dev-dx2d2y-log.tistory.com

내가 짠 JWTAuthorizationFilter는 OncePerRequestFilter를 상속하여 UsernamePasswordAuthenticationFilter의 역할을 대신 수행한다.

 

따라서 기존에 짠 코드에 SecurityFilterChain 속성들을 여럿 추가하여 제대로된 로직을 처리해보려한다.


다음 필터를 신뢰하기

protected void doFilterInternal(HttpServletRequest request,
                                HttpServletResponse response,
                                FilterChain filterChain) throws ServletException, IOException {
    String path = request.getRequestURI();
    System.out.println(path);

    if (path.startsWith("/signup") || path.startsWith("/oauth2") ||
        path.startsWith("/css") || path.startsWith("/js") || path.startsWith("/images")) {
        filterChain.doFilter(request, response);
        return;
    }

    Cookie[] cookies = request.getCookies();
    
    if (cookies == null) {
        throw new AuthenticationException("cookies is null");
    }
    ...
}

기존 방식에서는 인증에 실패한 경우 AuthenticationException을 던졌고, 만약 인증이 필요없는 요청에 대해서는 조건문을 사용하여 URL 주소를 검사시켰다. (permitAll()로 설정된 URL에 대해서도 똑같이 인증필터가 동작하기 때문에 이렇게 조건문을 사용해 따로 처리시켜 굳이 인증을 사용하지 않고 통과시키도록 설정했다.)

 

이러한 방식은 딱봐도 좋아보이지 않고, (설정이 바뀔 때마다 조건문을 변경해야하고... 등등) AuthenticationException을 던지는 구조는 설계상 좋지 않다고한다. 그 이유는 JWTAuthorizationFilter의 특성은 "토큰이 있으면 인증, 없으면 통과"가 기본 특성이다. 만약 인증이 안된 사용자가 있더라도 이 뒷체인에서 로그인이 안되었다고 감지해낼 수 있기 때문에 여기서 굳이 로그인 여부를 가려내지 않아도 된다고한다. 그저 다음 필터로 인증을 넘기면 된다고한다.

 

만약 유저의 요청에 JWT토큰이 없다면 뒷쪽필터로 인증을 넘기면, 뒤쪽필터에서 Authentication 객체가 SecurityConfig에 없으므로 미인증된 사용자라는 것을 알아채게되고 하는 식으로 동작한다. 이 뒷부분은 뒷쪽필터를 공부하면서 알아볼 예정

 

암튼 그래서 중요한 것은 유저의 JWT토큰이 없더라도 AuthenticationException을 던지면 안된다. 그냥 다음 필터로 인증과정을 넘겨야한다.

 

protected void doFilterInternal(HttpServletRequest request,
                                HttpServletResponse response,
                                FilterChain filterChain) throws ServletException, IOException {
    String path = request.getRequestURI();
    System.out.println(path);

    Cookie[] cookies = request.getCookies();
    if (cookies == null) {
        filterChain.doFilter(request, response);
        return;
    }
}

이런식으로..

 

package com.example.GoldenReport.Config;

import com.example.GoldenReport.Service.JWT.JWTProvider;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.coyote.BadRequestException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.*;

public class JWTAuthorizationFilter extends OncePerRequestFilter {

    private final JWTProvider jwtProvider;

    public JWTAuthorizationFilter(JWTProvider JWTprovider) {
        this.jwtProvider = JWTprovider;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String path = request.getRequestURI();
        System.out.println(path);

        Cookie[] cookies = request.getCookies();
        if (cookies == null) {
            filterChain.doFilter(request, response);
            return;
        }

        String accessToken = Arrays.stream(cookies)
                .filter(cookie -> "Authentication".equals(cookie.getName()))
                .map(Cookie::getValue)
                .findFirst()
                .orElse(null);

        if (accessToken == null) {
            filterChain.doFilter(request, response);
            return;
        }

        try {
            Optional<Map<String, Object>> optionalUser = jwtProvider.decodeJWT(accessToken, List.of("position"));
            if (optionalUser.isEmpty()) {
                filterChain.doFilter(request, response);
                return;
            }

            Map<String, Object> user = optionalUser.get();

            Authentication authentication = new UsernamePasswordAuthenticationToken(
                    user.get("subject"),
                    null,
                    AuthorityUtils.createAuthorityList(user.get("position").toString()));

            SecurityContextHolder
                    .getContext()
                    .setAuthentication(authentication);

            filterChain.doFilter(request, response);
        } catch (JwtException e) {
            e.printStackTrace();
            throw new BadRequestException("Invalid JWT Token", e);
        }
    }
}

다만 이후과정에서 잡아낼 수 없는 JWT토큰과 관련된 에러는 따로 예외처리를 통해 따로 예외를 던진다. BadRequestException은 AuthenticationException의 하위클래스로, 여기서 던진 에러는 EntryPoint에서 잡아낼 수 있다.


정석을 향해서

내가 사용한 JWT토큰은 단순한 방식이기도하고해서 단순히 저 JWTAuthorizationFilter 하나만으로도 처리할 수 있다.

 

실제로 정석적으로 처리하려면 위의 JWTAuthorizationFilter를 AuthenticationFilter로 삼아서 AuthenticationManager까지 구현해야한다. 그래서 해보려한다.

 

방향성은

 

1. 사용자가 어떤 방식으로든 JWT토큰을 넘긴다.

2. AuthenticationFilter는 JWT토큰을 받아서, Authentication 객체의 Credentials 부분에 JWT토큰을 넣는다.

3. AuthenticationManager는 AuthenticationProvider에게 인증을 위임하고, 인증이 성공적으로 끝나면 Authentication 객체의 username부분에 유저식별자를, Credentials 부분을 null로, Authorities 부분을 권한으로 설정한다.


AuthenticationToken

나는 개발할 때 가장 안쪽에서부터 개발하곤한다. 이번단계에서는 AuthenticationProvider부터 개발해서 AuthenticationFilter로 나오는 방식으로 개발할 생각이고, 가장 먼저 Authentication의 구현체인 JwtAuthenticationToken을 구현하도록한다.

 

Authentication의 구현은 추상메서드 AbstractAuthenticationToken을 구현하는 것으로 한다.

package com.example.GoldenReport.FilterChain;

import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;

public class JwtAuthenticationToken extends AbstractAuthenticationToken {
    private final Object principal;
    private final String token;

    public JwtAuthenticationToken(String token) {
        super((Collection<? extends GrantedAuthority>) null);
        this.principal = null;
        this.token = token;
        setAuthenticated(false);
    }

    public JwtAuthenticationToken(Object principal,
                                  Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.token = null;
        setAuthenticated(true);
    }


    @Override
    public @Nullable Object getCredentials() {
        return null;
    }

    public @Nullable Object getToken() {
        return token;
    }

    @Override
    public @Nullable Object getPrincipal() {
        return principal;
    }
}

JwtAuthenticationToken

원래 Authentication의 구현체는 principal, credentials, authorities로 이루어져있다. AbstractAuthenticationToken에 이미 Authorities가 구현되어있고, principal과 credentials는 구현되어있지 않다.

 

JwtAuthenticationToken은 credentials 대신에 토큰을 저장할 것이므로 principal, token, authorities로 구성했다. getCredentials 메서드는 Authentication 인터페이스에는 구현되어있으나 AbstractAuthenticationToken에는 구현되어있지 않아서 그냥 null을 반환하게하고, 실제로는 getToken() 메서드가 getCredentials 역할을 수행한다.

 

이 Authentication 구현체들은 대부분 불변객체로 설정되기 때문에, 이 Authenticaiton 구현체를 새로 만들어야할 때는 수정이 안되고 매번 새로 만들어야한다. 따라서 생성자를 여러 개 만들어서 그때그때 생성할 수 있도록한다. 앞서 말한 플로우에서 Authentication 객체는 2번과 3번에서 각각 필요하므로 생성자도 두 개


JwtUserDetails

Authentication 객체에 principal에 들어갈 유저의 정보를 나타낸다. 정석적인 구현이거나 아이디/비밀번호 기반 로그인에서는 UserDetails 객체를 상속받아서 사용하고, 간단한 경우에는 String을 사용하기도한다. 나는 UserDetails를 사용할 예정

 

package com.example.GoldenReport.FilterChain;

import lombok.Getter;
import org.jspecify.annotations.Nullable;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.Set;

public class JwtUserDetails implements UserDetails {

    @Getter
    private final String userId;

    private final Collection<? extends GrantedAuthority> authorities;

    public JwtUserDetails(String userId,
            Collection<? extends GrantedAuthority> authorities) {
        this.userId = userId;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public @Nullable String getPassword() {
        return null;
    }

    @Override
    public String getUsername() {
        return getUserId();
    }
}

UserDetails 객체를 상속했다.

getAuthorities() 메서드와 getPassword, getUsername 메서드는 상속받아서 사용했다. UserDetails의 getAuthorities와 Authentication 객체의 Authorities에 들어가는 정보는 같다고한다.

 

JWT토큰을 해독하여 사용자식별자만 저장할 것이므로 별도의 Credentials는 저장하지 않는다. 따라서 Credentials의 게터메서드도 null을 반환하도록 설정했다. userId의 게터메서드는 @Getter 어노테이션을 사용하여 getUserId() 메서드를 우선사용하는 것으로하고, 상속받은 getUsername() 메서드는 getUserId() 메서드를 호출하도록해 둘 다 사용가능하게했다.


JwtAuthenticationProvider

이제 본격적으로 인증을 관리하는 AuthenticationProvider를 사용해야한다. JWT토큰 인증방식만 사용할 것이므로 JwtAuthenticationProvider만 AuthenticationProvider로 사용한다.

 

package com.example.GoldenReport.FilterChain;

import com.example.GoldenReport.Service.JWT.JWTProvider;
import io.jsonwebtoken.ExpiredJwtException;
import org.jspecify.annotations.Nullable;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.List;
import java.util.Map;
import java.util.Optional;


public class JwtAuthenticationProvider implements AuthenticationProvider {
    private final JWTProvider jwtProvider;

    public JwtAuthenticationProvider(JWTProvider jwtProvider) {
        this.jwtProvider = jwtProvider;
    }

    @Override
    @Nullable
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        try {
            String accessToken = (String) authentication.getCredentials();

            if (accessToken == null) {
                throw new BadCredentialsException("accessToken is null");
            }

            Map<String, Object> decodedToken = jwtProvider.decodeJWT(accessToken,
                    List.of("position", "subject")).orElseThrow(() -> new BadCredentialsException("accessToken is not valid"));

            String subject = Optional.ofNullable(decodedToken.get("subject"))
                    .orElseThrow(() -> new BadCredentialsException("AccessToken's subject is not Valid"))
                    .toString();

            String position = Optional.ofNullable(decodedToken.get("position"))
                    .orElseThrow(() -> new BadCredentialsException("AccessToken's position is not Valid"))
                    .toString();

            UserDetails user = new JwtUserDetails(
                    subject,
                    AuthorityUtils.createAuthorityList(position)
            );

            return new JwtAuthenticationToken(
                user, AuthorityUtils.createAuthorityList(position)
            );

        } catch (ExpiredJwtException e) {
            e.printStackTrace();
            throw new BadCredentialsException("accessToken is expired");
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return (JwtAuthenticationToken.class.isAssignableFrom(authentication));
    }

}

JwtAuthenticationProvider

Authentication 객체를 매개변수로 받아서 credentials에서 JWT토큰을 뽑아서 subject (여기에 userId가 저장됨)와 position (권한)을 검사한다. 만약 이 과정에서 어느하나라도 null이거나, ExpiredJwtException이 발생하면 BadCredentialsException을 던진다.


AuthenticationFilter

AuthenticationManager는 굳이 다룰 필요가 없다. 왜냐하면 스프링에서 만든 ProviderManager의 기능 중에서 굳이 오버라이드 해야할 것이 없기 때문이기 때문에..

 

따라서 AuthenticationFilter를 만들기만하면 된다.

 

package com.example.GoldenReport.FilterChain;

import com.example.GoldenReport.Service.JWT.JWTProvider;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class JwtAuthorizationFilter extends OncePerRequestFilter {

    JWTProvider jwtProvider = new JWTProvider();

    AuthenticationManager authenticationManager = new ProviderManager(
            new JwtAuthenticationProvider(jwtProvider)
    );

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String path = request.getRequestURI();
        System.out.println(path);

        Cookie[] cookies = request.getCookies();
        if (cookies == null) {
            filterChain.doFilter(request, response);
            return;
        }

        String accessToken = Arrays.stream(cookies)
                .filter(cookie -> "Authentication".equals(cookie.getName()))
                .map(Cookie::getValue)
                .findFirst()
                .orElse(null);

        if (accessToken == null) {
            filterChain.doFilter(request, response);
            return;
        }

        Authentication authentication = new JwtAuthenticationToken(accessToken);
        try {
            Authentication returnedAuthentication = authenticationManager.authenticate(authentication);
        } catch (AuthenticationException e) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }
}

이렇게...?

이전의 JWTAuthorizationFilter 대신 이 JwtAuthorizationFilter를 사용한다. 이쪽 내용은 이전의 JWTAuthorizationFilter과 크게 다르지 않다. 다른점이라면.. JWTAuthorizationFilter에서의 인증과정을 빼고 ProviderManager에게 인증을 넘긴다.

 

ProviderManager에게 AuthenticationProvider를 어떻게 넘길까 생각해봤는데, SecurityConfig에서 미리 ProviderManager에게 AuthenticationProvider를 넘겨주어도 되지만.. 방식이 다소 복잡하고 (그리고 귀찮기도하고) 해서 그냥 AuthenticationFilter에서 ProviderManager에게 AuthenticationProvider를 넘겨주도록했다.

 

public ProviderManager(AuthenticationProvider... providers) {
    this(Arrays.asList(providers), null);
}

public ProviderManager(List<AuthenticationProvider> providers) {
    this(providers, null);
}

public ProviderManager(List<AuthenticationProvider> providers, @Nullable AuthenticationManager parent) { 
    ...
}

그래서 커스텀 AuthenicationFilter를 직접 ProviderManager를 불러서 인증과정을 거쳐야한다.

ProviderManager는 생성자 매개변수를 통해 AuthenticationProvider를 가진다. 여기서는 AuthenticationProvider 를 여러 개 생성해서 생성자로 전해주던가, 리스트로 넘길 수 있다.

 

또한, ProviderManager에게서 AuthenticationProvider 여러 개를 리스트로 넘기고, 추가로 ProviderNotFoundException 에러가 발생했을 때 대신 인증을 수행할 AuthenticationManager 객체를 넘겨줄 수도 있다.

 

암튼 JwtAuthorizationFilter에서 doFilterInternal() 메서드는 쿠키에 달린 JWT토큰을 가져오는 것까지는 기존방식과 같지만, JWT토큰을 해독하여 Authentication 객체를 바로 만드는, 약간의 약식이었던 기존과 달리 정석적인 방식으로 ProviderManager로 인증을 넘기고, ProviderManager는 생성자를 통해 주입받은 AuthenticationProvider에게 인증을 맡긴다. AuthenticationProvider는 JWT토큰 인증을 진행하고, Authentication 객체를 반환한다. 반환되는 값은 Authentication 객체의 하위클래스인 JwtAuthenticationToken을 반환한다.


성공/실패 시 계획

여기까지가 인증을 통해 Authentication 객체를 반환받는 것이고, 이제 제대로 반환이 됐으면 성공로직을, 중간에 에러가 발생했다면 실패로직을 돌려야한다.


실패

    private void doFailure(HttpServletRequest request,
                           HttpServletResponse response,
                           AuthenticationException authException) throws IOException, ServletException {
        SecurityContextHolder.clearContext();
        failureHandler.onAuthenticationFailure(request, response, authException);
    }

SecurityContextHolder를 비우고, failureHandler를 호출한다. 나는 RememberMe 토큰을 발급하지 않으므로 RememberMe 토큰을 제외한 기능을 개발했다.

 

failureHandler는 AuthenticationFailureHandler의 인스턴스다.ㅛ

 

성공

    private void doSuccess(HttpServletRequest request,
                           HttpServletResponse response,
                           Authentication authentication) throws IOException, ServletException {
        SecurityContextHolder.getContext().setAuthentication(authentication);

        publisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                authentication,
                this.getClass()
        ));

        successHandler.onAuthenticationSuccess(request, response, authentication);

    }

SecurityContextHolder에 Authentication을 저장한다. 그리고 InteractiveAuthenticationSuccessEvent를 발행한다. 나중에 리스너 기능을 배우면 써먹을 수 있다. 그리고 successHandler를 발급한다.


로그인필터와 인증필터

필터체인이 좀 헷갈려서 지금 약간 꼬였는데, 처음에는 JwtAuthorizationFilter에서 로그인 성공 시 successHandler로 연결시켜주려고 했다.

 

그런데 생각해보니 모든 인증과정에서 인증성공 시 successHandler를 동작시키면 모든요청마다 인증에 성공할 시 JWT토큰을 발급시킨다는 것인데, 따라서 AuthenticationFilter를 두 개로 나눠야한다. 두 과정은 로그인 성공 시 어떤 로직을 거치느냐에 차이가 있고 AuthenticationManager에게 인증을 넘기는 점은 동일하다.

 

1. AuthenticationFilter (로그인용)

로그인 요청 중에는 AuthenticationFilter에서 인증을 마치고, successHandler에게 인증 후 로직 처리과정을 맡기면 AuthenticationFilter에서는 맡은 바를 다 수행한 것이다. 따라서 인증이 끝났으면 successHandler를 호출하면 끝.

 

2. AuthorizationFilter (인증용)

로그인 요청이 아니기 때문에 인증을 거쳐야하는경우, AuthorizationFilter에서 인증을 마치고 다음 필터로 인증을 넘겨야한다. 따라서 filterChain.doFilter() 메서드를 호출해야한다는 뜻.

 

스프링에서는 역시 이 둘을 분리해서 사용 중이다. 로그인용 필터로는 UsernamePasswordAuthenticatinoFilter, OAuth2LoginAuthenticationFilter가 있고, 인증용 필터로는 BasicAuthenticationFilter 등이 있다. 전자는 로그인하려는 유저의 정보를 담아 인증을 맡긴 다음에 로그인 결과를 표출해야하고, 후자는 세션이나 JWT토큰 등 인증수단을 받아서 인증하면된다.

 

따라서 두 필터가 AuthenticationManager는 공용으로 사용할 수 있지만, 중요한 것은 AuthenticationProvider. 로그인용 AuthenticationProvider에서는 DB에 직접 조회해서 사용자가 있는지 검사해야하고, 인증용 AuthenticationProvider에서는 JWT토큰만 확인하는 식으로 동작한다.

 

내 방식에서는 OAuth2만 사용하기 때문에 SecurityConfig에서 자동으로 AuthenticationProvider를 ProviderManager에게 넣어주고, 만약 폼로그인으로 확장한다면 새로이 폼로그인용 필터들을 만들어서 ProviderManager에게 추가해줘야할 것이다.

 

package com.example.GoldenReport.FilterChain;

import com.example.GoldenReport.Service.JWT.JWTProvider;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Arrays;

public class JwtAuthorizationFilter extends OncePerRequestFilter {

    JWTProvider jwtProvider;

    private final AuthenticationFailureHandler failureHandler;

    private final AuthenticationSuccessHandler successHandler;

    private final ApplicationEventPublisher publisher;

    private final AuthenticationManager authenticationManager;

    public JwtAuthorizationFilter(AuthenticationFailureHandler authenticationFailureHandler,
                                  AuthenticationSuccessHandler authenticationSuccessHandler,
                                  ApplicationEventPublisher publisher,
                                  JWTProvider jwtProvider) {
        this.failureHandler = authenticationFailureHandler;
        this.successHandler = authenticationSuccessHandler;
        this.publisher = publisher;
        this.jwtProvider = jwtProvider;

        this.authenticationManager = new ProviderManager(
                new JwtAuthenticationProvider(jwtProvider)
        );
    }



    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String path = request.getRequestURI();
        System.out.println(path);

        Cookie[] cookies = request.getCookies();
        if (cookies == null) {
            filterChain.doFilter(request, response);
            return;
        }

        String accessToken = Arrays.stream(cookies)
                .filter(cookie -> "Authentication".equals(cookie.getName()))
                .map(Cookie::getValue)
                .findFirst()
                .orElse(null);

        if (accessToken == null) {
            filterChain.doFilter(request, response);
            return;
        }

        Authentication authentication = new JwtAuthenticationToken(accessToken);
        try {

            Authentication returnedAuthentication = authenticationManager.authenticate(authentication);
            SecurityContextHolder.getContext().setAuthentication(returnedAuthentication);
            filterChain.doFilter(request, response);

        } catch (AuthenticationException e) {
            doFailure(request, response, e);
        }
    }

    private void doFailure(HttpServletRequest request,
                           HttpServletResponse response,
                           AuthenticationException authException) throws IOException, ServletException {
        SecurityContextHolder.clearContext();
        failureHandler.onAuthenticationFailure(request, response, authException);
    }
}

암튼 그래서 로그인 성공 시에 다음 필터로 인증을 넘긴다.


그래서 지금까지 여태까지 필터에 대해서 알아보고 필터를 직접 구현해보았다.

 

1. 인증

1. AuthenticationFilter에서 유저의 Request를 기반으로 Authentication 객체를 생성하고 credentials 부분에 JWT토큰을 넣는다.

2. 이후 AuthenticationManager에서 AuthenticationProvider로 인증을 위임한다.

3. AuthenticationProvider에서는 JWT토큰을 해독하고, 얻은 유저식별자와 권한을 토대로 Authentication 객체를 만들어 반환한다.

4. AuthenticationFilter에서는 Authentication 객체를 받아서 다음필터로 인증을 계속 수행시킨다.

 

2. 로그인

1. AuthenticationFilter에서 유저의 Request에서 아이디/비밀번호를 받아서 Authentication 객체를 세팅한다.

2. AuthenticationManager에서 AuthenticationProvider로 인증을 위임한다.

3. AuthencationProvider에서는 Authentication 객체에서 얻은 정보를 토대로 DB에 접근해 실제 유저인지 검사한다.

4. 인증에 문제가 없다면 Authentication 객체를 새로 만들어 반환한다.

5. AuthenticationFilter에서는 Authentication 객체가 제대로 들어왔으면 SuccessHandler에게 다음 로직을 수행시킨다.

 

이러한 방식은 JWT토큰을 스프링 필터체인에 어떻게 올라타게 할 수 있는지 알아보기 위한 방법으로, 실제로는 필터체인보다는 그냥 하나의 필터에서 토큰을 처리하는 편이다.

 

https://dev-dx2d2y-log.tistory.com/216

 

[골든리포트!] 6) 필터체인에서 JWT필터 인증 처리하기

몇 가지 수정사항이 생겼다.GDG 플젝트랙에서는 모든 경로를 permitAll() 해주고 인증이 필요한 요청에 대해 헤더에 있는 JWT 토큰을 검사하는 방식으로 구현했다. 물론 이 방식은 딱히 좋은 편이 아

dev-dx2d2y-log.tistory.com

이런 방식은 이 게시글과 본 게시글의 윗부분에서 다루고 있다.

 

이 이후과정은 SecurityConfig에서 JwtAuthorizationFilter를 연결시켜줘야하는데, 이건 나중에.. 지금 DI와 new 연산자가 좀 섞여있어서 교통정리도 해야하고 좀 더 알아봐야하기 때문이다.

 

실제로는 하나의 필터에서 토큰을 처리하게할 생각이다. 지금은 스프링의 내부 필터체인에 대해서 공부도해보고, 나중에 세션인증이나 폼로그인과도 결합시키기 위해서 공부해보았다.

'개인 프로젝트 > [2026] 골든리포트!' 카테고리의 다른 글

[골든리포트!] 8) JwtAuthorizationFilter 최종정리 및 SecurityConfig에 등록하기  (0) 2026.02.18
[골든리포트!] 6) 필터체인에서 JWT필터 인증 처리하기  (0) 2026.02.13
[골든리포트!] 5) JWT 토큰 후속기능 처리하기  (0) 2026.02.12
[골든리포트!] 4) 스프링 세션 설정 및 JWT토큰 발급  (0) 2026.02.10
[골든리포트!] 3) Spring Security Config로 스프링 OAuth 다루기  (0) 2026.02.10
'개인 프로젝트/[2026] 골든리포트!' 카테고리의 다른 글
  • [골든리포트!] 8) JwtAuthorizationFilter 최종정리 및 SecurityConfig에 등록하기
  • [골든리포트!] 6) 필터체인에서 JWT필터 인증 처리하기
  • [골든리포트!] 5) JWT 토큰 후속기능 처리하기
  • [골든리포트!] 4) 스프링 세션 설정 및 JWT토큰 발급
Radiata
Radiata
개발을 합니다.
  • Radiata
    DDD
    Radiata
  • 전체
    오늘
    어제
    • 분류 전체보기 (211) N
      • 신년사 (3)
        • 2025년 (2)
        • 2026년 (1)
      • CS (59) N
        • JVM (12)
        • 백엔드 (20) N
        • 언어구현 (1)
        • 객체지향 (1)
        • 논리회로 (5)
        • 컴퓨터구조 (9)
        • 데이터베이스 (1)
        • 컴퓨터 네트워크 (10)
      • 언어공부 (64)
        • Java | Kotlin (48)
        • JavaScript | TypeScript (9)
        • C | C++ (6)
      • 개인 프로젝트 (11)
        • [2025] Happy2SendingMails (3)
        • [2026] 골든리포트! (8)
        • [2026] 순수자바로 개발하기 (0)
        • 기타 이것저것 (0)
      • 팀 프로젝트 (29)
        • [2025][GDG]홍대 맛집 아카이빙 프로젝트 (29)
      • 알고리즘 (13)
        • 백준풀이기록 (11)
      • 놀이터 (0)
      • 에러 수정일지 (2)
      • 고찰 (24)
        • CEOS 23기 회고록 (2)
  • 블로그 메뉴

    • CS
    • 언어공부
    • 개인 프로젝트
    • 팀 프로젝트
    • 알고리즘
    • 고찰
    • 신년사
    • 컬러잇 개발블로그
  • 링크

    • 컬러잇 개발블로그
  • 공지사항

  • 인기 글

  • 태그

    144
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
Radiata
[골든리포트!] 7) JWT토큰 인증과정을 Spring Security 필터체인에 태워보기
상단으로

티스토리툴바