본문 바로가기

개발/java_spring

[Spring Boot] Jwt

반응형

gradle 설정

implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'

 

JwtTokenProvider 생성

import java.security.Key;
import java.util.Base64;
import java.util.Date;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.Keys;
import lombok.extern.log4j.Log4j2;

@Log4j2
@Component
public class JwtTokenProvider implements InitializingBean{
    private final String secret;
    private final long tokenValidmils;
    private Key key;
    
    public JwtTokenProvider(@Value("${jwt.secret}") String secret, @Value("${jwt.validSeconds}") Long validSeconds) {
        this.secret = secret;
        this.tokenValidmils = validSeconds * 1000;
    }

    /*
     * token 생성에 사용될 key 생성
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        
      byte[] keyBytes = Base64.getDecoder().decode(secret);
      this.key = Keys.hmacShaKeyFor(keyBytes);
    }

    /*
     * token 생성
     */
    public String generateTokenString(String id) {
        Date now = new Date();
        Date expDate = new Date(now.getTime() + tokenValidmils);

        return Jwts.builder()
        .setSubject(id)
        .setIssuedAt(new Date())
        .setExpiration(expDate)
        .signWith(key, SignatureAlgorithm.HS512)
        // .signWith( SignatureAlgorithm.HS512, key) //Deprecated
        .compact();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            // Jwts.parser().setSigningKey(key).parseClaimsJws(token); //Deprecated
            return true;
         } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
            log.info("잘못된 JWT 서명입니다.");
         } catch (ExpiredJwtException e) {
            log.info("만료된 JWT 토큰입니다.");
         } catch (UnsupportedJwtException e) {
            log.info("지원되지 않는 JWT 토큰입니다.");
         } catch (IllegalArgumentException e) {
            log.info("JWT 토큰이 잘못되었습니다.");
         }
         return false;
    }

    /*
     * token에서 id 추출
     */
    public Authentication getAuthenticateAction(String token) {
        Claims claims = (Claims) Jwts.parserBuilder()
                        .setSigningKey(key)
                        .build()
                        .parse(token)
                        .getBody();
        log.info("get authentication from token : " + claims);
                        
        // Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody(); //Deprecated

        return new UsernamePasswordAuthenticationToken(claims.getSubject(), token, null);
    }
    
}

JwtFilter 생성

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.GenericFilter;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;

import lombok.extern.log4j.Log4j2;

@Log4j2
public class JwtAuthenticationFilter extends GenericFilter{

    public static final String AUTHORIZATION_HEADER = "Authorization";
    private JwtTokenProvider tokenProvider;

    public JwtAuthenticationFilter(JwtTokenProvider tokenProvider) {
        this.tokenProvider = tokenProvider;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String jwt = resolveToken(httpServletRequest);
        String requestURI = httpServletRequest.getRequestURI();

        if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
            Authentication authentication = tokenProvider.getAuthenticateAction(jwt);
            SecurityContextHolder.getContext().setAuthentication(authentication);
         } else {
            log.debug("유효한 JWT 토큰이 없습니다, uri: {}", requestURI);
         }
   
         chain.doFilter(request, response);
    }
    
    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
           return bearerToken.substring(7);
        }
        return null;
     }
}

Security에 Filter 적용

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .cors().and()
        .csrf().disable()
        .addFilterBefore(new JwtAuthenticationFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class)
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .
        .
        .
        .
        .
        .
        .

Controller에서 사용자 인증 후 토큰 생성

// id로 jwt token 생성
String jwtToken = jwtTokenProvider.generateTokenString(id);

토큰 체크 후에는 Principal 객체 통해 사용자 정보 get

반응형