Spring Security, JWT 인증 서버 구현 3

1c2·2025년 1월 23일
0

스프링 시큐리티

목록 보기
5/5
post-thumbnail

TokenProvider의 claim을 통해 Authentication을 생성하는 함수를 만들어 보겠습니다.
위 사진에서 2번 과정에 해당합니다.

jwt token이 유효하다면 claim을 분석해서 Authentication객체인 UsernamePasswordAuthenticationToken을 만들고 이를 SecurityContext에 넣어주는 작업입니다. 이걸 해야 Spring Security가 인증된 사용자의 요청이라고 판단하고 이후의 필터를 통과할 수 있게 해줍니다.
먼저 UsernamePasswordAuthenticationToken객체를 조립하는 함수입니다.

TokenProvider

...
    public Authentication getAuthentication(String token, Claims claims) {
        Collection<? extends GrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());

        UserPrinciple principle = new UserPrinciple(claims.getSubject(), claims.get(USERNAME_KEY, String.class), authorities);

        return new UsernamePasswordAuthenticationToken(principle, token, authorities);
    }
...

이제 Provider도 Config파일에 등록해서 Bean으로 등록해줍니다.

JwtConfig

package com.jwt.domain.config;

@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(JwtProperties.class)
public class JwtConfig {

    @Bean
    public TokenProvider jwtTokenProvider(JwtProperties jwtProperties) {
        return new TokenProvider(jwtProperties.getSecret(), jwtProperties.getAccessTokenValidityInSeconds());
    }

    ...
}

이제 jwtFilter를 구현하겠습니다. 기본적으로 jwtFilter는 OncePerRequestFilter를 extends합니다. OncePerRequestFilter는 왜 쓸까요?

OncePerRequestFilter vs GenericFilterBean

OncePerRequestFilterGenericFilterBean를 extends하여 한번 만 실행되도록 보장합니다.
(GenericFilterBean은 여러 번 실행될 수 있습니다.)
Jwt유효성 검증은 요청당 한 번만 실행이 보장되어야 하므로 OncePerRequestFilter를 상속하는 것이 적절합니다.

JwtFilter

먼저 resolveToken입니다. 이 함수는 요청의 헤더에서 Autherization 헤더를 가지고 와서 앞에 bearer가 있는지 확인하고, 있다면 이 부분을 떼주는 역할을 합니다.

package com.jwt.domain.login.jwt;


@Slf4j
@Component
@RequiredArgsConstructor
public class JwtFilter extends OncePerRequestFilter {

    public static final String AUTHORIZATION_HEADER = "Authorization";
    private static final String BEARER_REGEX = "Bearer ([a-zA-Z0-9_\\-\\+\\/=]+)\\.([a-zA-Z0-9_\\-\\+\\/=]+)\\.([a-zA-Z0-9_.\\-\\+\\/=]*)";
    private static final Pattern BEARER_PATTERN = Pattern.compile(BEARER_REGEX);
    
    private final TokenProvider tokenProvider;

    ...

    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
        if(bearerToken != null && BEARER_PATTERN.matcher(bearerToken).matches()) {
            return bearerToken.substring(7);
        }

        return null;
    }
    ...
}

토큰을 상황에 따라 어떻게 처리할 지 구분한 private함수를 다음과 같이 구현해주겠습니다.

    private void handleValidToken(String token, TokenValidationResult tokenValidationResult) {
        Authentication authentication = tokenProvider.getAuthentication(token, tokenValidationResult.claims());
        SecurityContextHolder.getContext().setAuthentication(authentication);
        log.info("AUTH SUCCESS : {}", authentication.getName());
    }

    private void handleWrongToken(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain,
                                  TokenValidationResult tokenValidationResult) throws IOException, ServletException {
        request.setAttribute("result", tokenValidationResult);
        filterChain.doFilter(request, response);
    }

    private void handleMissingToken(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        request.setAttribute("result",
                new TokenValidationResult(TokenStatus.WRONG_AUTH_HEADER, null, null, null));
        filterChain.doFilter(request, response);
    }

이제 doFilterInternal를 Override해줘야 합니다. doFilterInternal에서 필터의 실제 로직을 구현해야 합니다.

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String token = resolveToken(request);

        if(!StringUtils.hasText(token)) {
            handleMissingToken(request, response, filterChain);
            return;
        }

        TokenValidationResult tokenValidationResult = tokenProvider.validateToken(token);

        if(!tokenValidationResult.isValid()) {
            handleWrongToken(request, response, filterChain, tokenValidationResult);
            return;
        }

        handleValidToken(token, tokenValidationResult);
        filterChain.doFilter(request, response);
    }
  1. 토큰이 없다.
  2. 토큰이 있는데 유효하지 않다.
  3. 토큰이 있고 유효하다.

3가지 경우로 나눠져 있습니다.

0개의 댓글

관련 채용 정보