[Spring] JwtTokenProvider 구현

WOOK JONG KIM·2022년 11월 8일
0
post-thumbnail

JWT 토큰을 생성하는데 필요한 정보를 UserDetails에서 가져올 수 있기 때문에 JWT 토큰을 생성하는 JwtTokenProvider를 생성

@Component
@RequiredArgsConstructor
public class JwtTokenProvider {

    private final Logger LOGGER = LoggerFactory.getLogger(JwtTokenProvider.class);
    private final UserDetailsService userDetailsService;

    @Value("${springboot.jwt.secret}")
    private String secretKey = "secretKey";
    private final long tokenValidMillisecond = 1000L * 60* 60;

    @PostConstruct
    protected void init(){
        LOGGER.info("[init] JwtTokenProvider 내 secretKey 초기화 시작");
        secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8));

        LOGGER.info("[init] JwtTokenProvider 내 SecretKey 초기화 완료");
    }

    public String createToken(String userUid, List<String> roles){
        LOGGER.info("[createToken] 토큰 생성 시작");
        Claims claims = Jwts.claims().setSubject(userUid);
        claims.put("roles", roles);
        Date now = new Date();

        String token = Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(new Date(now.getTime() + tokenValidMillisecond))
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();

        LOGGER.info("[createToken] 토큰 생성 완료");
        return token;
    }

    public Authentication getAuthentication(String token){
        LOGGER.info("[getAuthentication] 토큰 인증 정보 조회 시작");
        UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUsername(token));
        LOGGER.info("[getAuthentication] 토큰 인증 정보 조회 완료, UserDetails UserName : {}",
                userDetails.getUsername());
        return new UsernamePasswordAuthenticationToken(userDetails,"", userDetails.getAuthorities());
    }

    public String getUsername(String token){
        LOGGER.info("[getUsername] 토큰 기반 회원 구별 정보 추출");
        String info = Jwts.parser().setSigningKey(secretKey).parseClaimsJwt(token).getBody()
                .getSubject();
        LOGGER.info("[getUsername] 토큰 기반 회원 구별 정보 추출 완료, info: {}", info);
        return info;
    }

    public String resolveToken(HttpServletRequest request){
        LOGGER.info("[resolveToken] HTTP 헤더에서 Token 값 추출");
        return request.getHeader("X-AUTH-TOKEN");
    }

    public boolean validateToken(String token){
        LOGGER.info("[validateToken] 토큰 유효 체크 시작 ");
        try{
            Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
            return !claims.getBody().getExpiration().before(new Date());
        } catch(Exception e){
            LOGGER.info("[validateToken] 토큰 유효 체크 예외 발생");
            return false;
        }
    }
}

우선 토큰 생성을 하기 위해 SecretKey 값을 정의 하였음

@Value 값은 properties 파일에서 정의 가능

spring.jwt.secret = flature!@#

init() 메서드

 	@PostConstruct
    protected void init(){
        LOGGER.info("[init] JwtTokenProvider 내 secretKey 초기화 시작");
        System.out.println(secretKey)
        secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes(StandardCharsets.UTF_8));
        System.out.println(secretKey)

        LOGGER.info("[init] JwtTokenProvider 내 SecretKey 초기화 완료");
    }

@PostConstruct는 해당 객체가 빈 객체로 주입된 이후 수행되는 메서드를 가리킴

앞서 JwtTokenProvider 클래스 작성전 @Component를 지정하여 애플리케이션을 실행하면 빈으로 자동 주입
-> 그때 @PostConstruct 지정돼 있는 init() 메서드가 자동으로 실행

init 메서드에서는 secretkey를 base64 형식으로 인코딩 하였음

인코딩 전 원본 문자열
flature!@#

Base64 인코딩 결과
ZmxhdHVyZSFAIw==

createToken()

public String createToken(String userUid, List<String> roles){
        LOGGER.info("[createToken] 토큰 생성 시작");
        Claims claims = Jwts.claims().setSubject(userUid);
        claims.put("roles", roles);
        Date now = new Date();

        String token = Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(new Date(now.getTime() + tokenValidMillisecond))
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();

        LOGGER.info("[createToken] 토큰 생성 완료");
        return token;
    }

JWT 토큰에 값을 넣기 위해 Claims 객체 생성

setSubject() 메서드를 통해 sub 속성에 값을 추가하려면 User의 uid 값을 사용

그 다음에는 해당 토큰을 사용하는 사용자의 권한을 확인할 수 있는 role 값을 별개로 추가

Jwts.builder()를 사용해 토큰 생성

getAuthentication() 메서드

public Authentication getAuthentication(String token){
        LOGGER.info("[getAuthentication] 토큰 인증 정보 조회 시작");
        UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUsername(token));
        LOGGER.info("[getAuthentication] 토큰 인증 정보 조회 완료, UserDetails UserName : {}",
                userDetails.getUsername());
        return new UsernamePasswordAuthenticationToken(userDetails,"", userDetails.getAuthorities());
    }

이 메서드는 필터에서 인증이 성공했을 때 SecurityContextHolder에 저장할 Authentication 생성하는 역할

Authentication을 구현하는 편한 방법은 UsernamePasswordAuthenticationToken를 사용하는 것

UsernamePasswordAuthenticationToken은 AbstractAuthenticationToken을 상속 받고 있는데 이는 Authentication< interface >CredentialsContainer < interface > 의 구현체

이러한 토큰 클래스를 사용하려면 초기화를 위한 UserDetails가 필요
-> 여기선 UserDetailsService를 통해 가져옴

이때 사용한 Username은 밑과 같이 구현

getUsername()

 public String getUsername(String token){
        LOGGER.info("[getUsername] 토큰 기반 회원 구별 정보 추출");
        String info = Jwts.parser().setSigningKey(secretKey).parseClaimsJwt(token).getBody()
                .getSubject();
        LOGGER.info("[getUsername] 토큰 기반 회원 구별 정보 추출 완료, info: {}", info);
        return info;
    }

Jwts.parser() 를 통해 secretkey를 설정하고 클레임을 추출하여 토큰을 생성할때 넣었던 sub 값을 추출

resolveToken()

public String resolveToken(HttpServletRequest request){
        LOGGER.info("[resolveToken] HTTP 헤더에서 Token 값 추출");
        return request.getHeader("X-AUTH-TOKEN");
    }

이는 HttpServletRequest를 파라미터로 받아 헤더 값으로 전달된 X-AUTH-TOKEN 값을 가져와 리턴

클라이언트가 헤더를 통해 애플리케이션 서버로 JWT 토큰 값을 전달해야 정상적인 추출이 가능

validateToken() 메서드

 public boolean validateToken(String token){
        LOGGER.info("[validateToken] 토큰 유효 체크 시작 ");
        try{
            Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
            return !claims.getBody().getExpiration().before(new Date());
        } catch(Exception e){
            LOGGER.info("[validateToken] 토큰 유효 체크 예외 발생");
            return false;
        }
    }

이 메서드는 토큰을 전달받아 클레임의 유효기간을 체크하고 boolean 타입의 값을 리턴하는 역할

profile
Journey for Backend Developer

0개의 댓글