Spring

Spring 숙련 3 (JWT 다루기)

sehunbang 2024. 1. 24. 20:55

1 JWT dependency 추가

 

dependencies {
    // JWT
    compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
    runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
    runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'

 

2. application.properties security 추가

 

jwt.secret.key=7Iqk7YyM66W07YOA7L2U65Sp7YG065+9U3ByaW5n6rCV7J2Y7Yqc7YSw7LWc7JuQ67mI7J6F64uI64ukLg==

 

3. JwtUtil.java 생성

 

Util 클래스란 특정 매개 변수(파라미터)에 대한 작업을 수행하는 메서드들이 존재하는 클래스를 뜻합니다. 쉽게 설명하자면 다른 객체에 의존하지 않고 하나의 모듈로서 동작하는 클래스라고 생각하시면 좋습니다.

 

<JWT 관련 기능>

1. JWT 생성

2. 생성된 JWT를 Cookie에 저장

3. Cookie에 들어있던 JWT 토큰을 Substring

4. JWT 검증

5. JWT에서 사용자 정보 가져오기

 

쿠키 직접 만들기 vs 그냥 Response 헤더에  넣어 로 보내기 :

이번에는 쿠키 직접 만들기 해봄.

@Component
public class JwtUtil {
    // Header KEY 값
    public static final String AUTHORIZATION_HEADER = "Authorization";
    // 사용자 권한 값의 KEY
    public static final String AUTHORIZATION_KEY = "auth";
    // Token 식별자
    public static final String BEARER_PREFIX = "Bearer ";
    // 토큰 만료시간
    private final long TOKEN_TIME = 60 * 60 * 1000L; // 60분

    @Value("${jwt.secret.key}") // Base64 Encode 한 SecretKey
    private String secretKey;
    private Key key;
    private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

    // 로그 설정
    public static final Logger logger = LoggerFactory.getLogger("JWT 관련 로그");

    @PostConstruct
    public void init() {
        byte[] bytes = Base64.getDecoder().decode(secretKey);
        key = Keys.hmacShaKeyFor(bytes);
    }

 

Base64로 Encode된 Secret Key를 properties에 작성해두고 @Value를 통해 가져옵니다.

 

 Encode된 Secret Key를 Decode 해서 사용합니다.

 Key는 Decode된 Secret Key를 담는 객체입니다.

 

@PostConstruct : 딱 한 번만 받아오면 되는 값을 사용 할 때,(요청을 새로 호출하는 실수를 방지하기 위해.)

JwtUtil 클래스의 생성자 호출 이후에 실행되어 Key 필드에 값을 주입 해줍니다.

 

암호화 알고리즘은 HS256 알고리즘을 사용

 

Bearer 란 JWT 혹은 OAuth에 대한 토큰을 사용한다는 표시

로깅이란 애플리케이션이 동작하는 동안 프로젝트의 상태나 동작 정보를 시간순으로 기록

( 우리는 Logback 로깅 프레임워크를 사용해서 로깅 )

 

잠깐만 !

아까 application.properties 추가한 security 값을 받아 오는 방법!?

 

이것을 가겨 올때 @value("{키값 이름}")

 으로 하면 가져온다.

 

Entity 클래스 만들기. (UsaerRoleEnum)

 

public enum UserRoleEnum {
    USER(Authority.USER),  // 사용자 권한
    ADMIN(Authority.ADMIN);  // 관리자 권한

    private final String authority;

    UserRoleEnum(String authority) {
        this.authority = authority;
    }

    public String getAuthority() {
        return this.authority;
    }

    public static class Authority {
        public static final String USER = "ROLE_USER";
        public static final String ADMIN = "ROLE_ADMIN";
    }
}

 

 

JWT 관련 기능 만들기

 

토큰생성

// 토큰 생성
public String createToken(String username, UserRoleEnum role) {
    Date date = new Date();

    return BEARER_PREFIX +
            Jwts.builder()
                    .setSubject(username) // 사용자 식별자값(ID)
                    .claim(AUTHORIZATION_KEY, role) // 사용자 권한
                    .setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 만료 시간
                    .setIssuedAt(date) // 발급일
                    .signWith(key, signatureAlgorithm) // 암호화 알고리즘
                    .compact();
}

 

JWT 쿠키에 저장

// JWT Cookie 에 저장
public void addJwtToCookie(String token, HttpServletResponse res) {
    try {
        token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서 encoding 진행

        Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
        cookie.setPath("/");

        // Response 객체에 Cookie 추가
        res.addCookie(cookie);
    } catch (UnsupportedEncodingException e) {
        logger.error(e.getMessage());
    }
}

 

JWT 토큰 substring

// JWT 토큰 substring
public String substringToken(String tokenValue) {
    if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
        return tokenValue.substring(7);
    }
    logger.error("Not Found Token");
    throw new NullPointerException("Not Found Token");
}

//

JWT 검중

// 토큰 검증
public boolean validateToken(String token) {
    try {
        Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
        return true;
    } catch (SecurityException | MalformedJwtException | SignatureException e) {
        logger.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
    } catch (ExpiredJwtException e) {
        logger.error("Expired JWT token, 만료된 JWT token 입니다.");
    } catch (UnsupportedJwtException e) {
        logger.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
    } catch (IllegalArgumentException e) {
        logger.error("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
    }
    return false;
}

 

JWT 에서 정보 가져오기

public Claims getUserInfoFromToken(String token) {
    return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}

 

 

테스트 코드

AuthController.java 에

@RequiredArgsConstructor
private final JwtUtil jwtUtil;

추가 후 

@GetMapping("/create-jwt")
public String createJwt(HttpServletResponse res) {
    // Jwt 생성
    String token = jwtUtil.createToken("Robbie", UserRoleEnum.USER);

    // Jwt 쿠키 저장
    jwtUtil.addJwtToCookie(token, res);

    return "createJwt : " + token;
}

@GetMapping("/get-jwt")
public String getJwt(@CookieValue(JwtUtil.AUTHORIZATION_HEADER) String tokenValue) {
    // JWT 토큰 substring
    String token = jwtUtil.substringToken(tokenValue);

    // 토큰 검증
    if(!jwtUtil.validateToken(token)){
        throw new IllegalArgumentException("Token Error");
    }

    // 토큰에서 사용자 정보 가져오기
    Claims info = jwtUtil.getUserInfoFromToken(token);
    // 사용자 username
    String username = info.getSubject();
    System.out.println("username = " + username);
    // 사용자 권한
    String authority = (String) info.get(JwtUtil.AUTHORIZATION_KEY);
    System.out.println("authority = " + authority);

    return "getJwt : " + username + ", " + authority;
}