포스트

03.JWT 발급 및 검증

JWT 발급과 검증

  • 로그인 시 -> 성공 -> JWT 발급
  • 접근 시 -> JWT 검증

JWT 구조

  • JWT는 Header.Payload.Signature 구조로 이루어져 있다
  • Header
    • JWT임을 명시
    • 사용된 암호화 알고리즘
  • Payload
    • 정보
  • Signature
    • 암호화 알고리즘( BASE64(Header) + BASE64(Payload) + 암호화키 )


  • JWT의 특징은 내부 정보를 단순 BASE64 방식으로 인코딩하기 때문에 외부에서 쉽게 디코딩할 수 있다
  • 따라서, 외부에서 열람해도 되는 정보를 담아야한다.
  • 사용 이유는 토큰 자체의 발급처를 확인하기 위해서 사용


암호화 방식
  • 암호화 종류
  • 양방향
    • 대칭키 - HS256, HS512
    • 비대칭키
  • 단방향
암호화 키 저장
  • 암호화 키는 하드코딩 방식으로 구현 내부에 탑재하는 것을 지양하기 때문에 변수 설정 파일에 저장한다

  • application.yml

1
2
3
4
5
6
7
spring:
  jwt:
    header: Authorization
    #HS512 알고리즘을 사용할 것이기 때문에 512bit, 즉 64byte 이상의 secret key를 사용해야 한다.
    #echo 'silvernine-tech-spring-boot-jwt-tutorial-secret-silvernine-tech-spring-boot-jwt-tutorial-secret'|base64
    secret: c2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQtc2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQK
    token-validity-in-seconds: 86400
  • secret에 해당하는 문자열은 개발자가 임의대로 작성하면 된다.
  • 위의 경우는 HS512를 사용했기 때문에 최대한 길게 512비트 이상 되게끔 작성
  • 512비트 = 64바이트 : char 64개 이상

  • 나의 경우에는 jwt.yml 파일을 생성하여 만들었다
1
2
3
4
secret-key: testSecretKey20240229testSecretKey20240229testSecretKey20240229testSecretKey20240229
access-expiration-hours:  36000000
refresh-expiration-hours: 1008000000
issuer: jwKim
JWTUtil
  • 파일명 JWTUtil


  • 사용 목적
  • 토큰 Payload에 저장될 정보
    • username
    • role
    • 생성일
    • 만료일 등등…
  • JWTUtil 구현 메소드
    • JWTUtil 생성자 (lombok 사용)
    • username 확인 메소드
    • role 확인 메소드
    • 만료일 확인 메소드


  • JWT는 0.12.3 버전과 0.11.5 버전에 따라 들어오는 내용이 다르다.


  • 0.12.3 버전
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Component
public class JWTUtil {

  private SecretKey secretKey;

  public JWTUtil(@Value("${spring.jwt.secret}")String secret) {
    secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
  }

  public String getUsername(String token) {
    return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class);
  }

  public String getRole(String token) {
    return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class);
  }

  public Boolean isExpired(String token) {
    return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date());
  }

  public String createJwt(String username, String role, Long expiredMs) {
    return Jwts.builder()
            .claim("username", username)
            .claim("role", role)
            .issuedAt(new Date(System.currentTimeMillis()))
            .expiration(new Date(System.currentTimeMillis() + expiredMs))
            .signWith(secretKey)
            .compact();
  }
}
  • 0.11.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@PropertySource("classpath:jwt.yml") // 소스파일 지정
@Component
public class JWTUtil {

  private Key key;

  public JWTUtil(@Value("${secret-key}")String secret) {
    byte[] byteSecretKey = Decoders.BASE64.decode(secret);
    key = Keys.hmacShaKeyFor(byteSecretKey);
  }

  // 검증진행 시작
  public String getUsername(String token) {
    return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().get("username", String.class);
  }

  public String getRole(String token) {
    return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().get("role", String.class);
  }

  public Boolean isExpired(String token) {
    return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().getExpiration().before(new Date());
  }

  // 검증 종료

  // 토큰 생성
  public String createJwt(String username, String role, Long expiredMs) {
    Claims claims = Jwts.claims();
    claims.put("username", username);
    claims.put("role", role);

    return Jwts.builder()
            .setClaims(claims)
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + expiredMs))
            .signWith(key, SignatureAlgorithm.HS256)
            .compact();
  }
}
  • 검증
  • Jwts.parserBuilder()를 통해 전달 받은 토큰의 내용 확인
  • 토큰 생성
  • 로그인 성공 시, SuccessfulHandler를 통해서 username, role, expiredMs 를 전달 받아서 토큰을 생성해서 응답해준다
참고 사이트

개발자 유미