본문 바로가기

프로젝트

[ 프로젝트 ] Java Spring에 JWT 적용

JWT Token 파일 구성

1. SecurityConfig.java

Java Spring Security에 대한 설정과 JWT 필터 등록을 한다. @EnableWebSecurity 어노테이션을 추가해줘야 하며, 백엔드 상황에 따라 접근 정책을 작성해주면 된다.

 

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors()
                .and()
                .csrf()
                .disable()
                .exceptionHandling()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeHttpRequests(
                        (request) -> request.requestMatchers( "/" )
                                .permitAll()
                                .anyRequest()
                                .authenticated()
                )
                .httpBasic(Customizer.withDefaults())
                .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

 

2. JwtAuthenticationFilter

Java Spring Security에 등록할 JWT 필터 세부 구현사항을 정의한다. Java Spring에서 제공하는 OncePerRequestFilter를 상속받아 doFilterInternal를 재정의한다. 

 

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            var jwt = getJwtFromRequest(request);
            if (StringUtils.hasText(jwt) && JwtTokenProvider.validateToken(jwt)) {
                var userId = JwtTokenProvider.parseUserId(jwt);
                var authentication = new UserAuthentication(userId);

                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            } else {
                if (!StringUtils.hasText(jwt)) {
                    request.setAttribute("unauthorization", "401 인증키 없음.");
                }

                if (JwtTokenProvider.validateToken(jwt)) {
                    request.setAttribute("unauthorization", "401-001 인증키 만료.");
                }
            }
        } catch (Exception ex) {
            System.out.println("Could not set user authentication in security context");
        }

        filterChain.doFilter(request, response);
    }
}

 

3. JwtTokenProvider 

JWT Token에 관련된 필드, 메서드를 정의한다. Token 생성(Access, Refresh Token), 검증, 디코딩 등을 구체적인 구현 사항을 작성한다.  

 

public class JwtTokenProvider {

    private static final Key SIGNING_KEY = getSigningKey();
    private static final String secretKey = "secertKey";
    private static final int ACCESS_TOKEN_DURATION_SECONDS = 60;
    private static final int REFRESH_TOKEN_DURATION_SECONDS = 60 * 30;

    public static TokenResponse generateToken(Long userId) {
        var now = Instant.now();
        var expiryDateOfAccessToken = now.plusSeconds(ACCESS_TOKEN_DURATION_SECONDS);
        var expiryDateOfRefreshToken = now.plusSeconds(REFRESH_TOKEN_DURATION_SECONDS);

        String accessToken = Jwts.builder()
                .setClaims(Map.of(
                        "userId", userId,
                        "iat", now.getEpochSecond(),
                        "exp", expiryDateOfAccessToken.getEpochSecond()
                ))
                .signWith(SIGNING_KEY, SignatureAlgorithm.HS256)
                .compact();

        String refreshToken = Jwts.builder()
                .setClaims(Map.of(
                        "userId", userId,
                        "iat", now.getEpochSecond(),
                        "exp", expiryDateOfRefreshToken.getEpochSecond()
                ))
                .signWith(SIGNING_KEY, SignatureAlgorithm.HS256)
                .compact();

        return TokenResponse.builder()
                .accessToken(accessToken)
                .accessExpiredDate(expiryDateOfAccessToken.atZone(ZoneId.systemDefault()).toLocalDateTime())
                .refreshToken(refreshToken)
                .refreshExpiredDate(expiryDateOfRefreshToken.atZone(ZoneId.systemDefault()).toLocalDateTime())
                .build();
    }

    public static Long parseUserId(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(SIGNING_KEY)
                .build()
                .parseClaimsJws(token)
                .getBody()
                .get("userId", Long.class);
    }

    public static boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(SIGNING_KEY).build().parseClaimsJws(token);
            System.out.println(SIGNING_KEY);
            return true;
        } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            System.out.println("Invalid JWT Token");
        } catch (ExpiredJwtException e) {
            System.out.println("Expired JWT Token");
        } catch (UnsupportedJwtException e) {
            System.out.println("Unsupported JWT Token");
        } catch (IllegalArgumentException e) {
            System.out.println("JWT claims string is empty.");
        }
        return false;
    }

    public static Key getSigningKey() {
        return Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
    }

}

 

4. UserAuthentication

JWT Token에 담을 Authentication 정보를 설정한다. Java Spring에서 지원하는 AbstractAuthenticationToken을 상속 받아 작성을 하며, 상황에 맞게 setAuthenticated, getCredentials같은 메서드를 재정의한다.

 

public class UserAuthentication extends AbstractAuthenticationToken {
    private final AuthPayload authPayload;

    public UserAuthentication(Long userId) {
        super(null);
        this.authPayload = new AuthPayload(userId);
        setAuthenticated(true);
    }

    @Override
    public void setAuthenticated(boolean authenticated) {
        super.setAuthenticated(authenticated);
    }

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

    @Override
    public AuthPayload getPrincipal() {
        return authPayload;
    }
}

JWT Token 적용 결과

위와 같이 4개의 파일을 추가적으로 작성하면 정상적으로 JWT Token이 발급되는 것을 확인할 수 있다. 또한 그 Token을 통해서 백엔드에 접근이 이루어지는 것을 확인할 수 있다.

 

Debug 모드 사용 

프로젝트에 JWT 적용할 때 문제가 발생할 수 있다. 이 경우 Java Security Debug 모드를 사용 하면 좀 더 쉽게 해결하 수 있다. Debug 모드 적용은 SecurityConfig 파일의 @EnableWebSecurity 어노테이션에 debug=true 값을 주면 된다.

 

@EnableWebSecurity(debug = true)
public class SecurityConfig {
  ...
}