[Spring Security] JWT

zini9188·2023년 3월 23일
0

Spring Security

목록 보기
6/6

JWT

JWT란?

  • JSon Web Token는 데이터를 안전하고 간결하게 전송하기 위해 고안된 인터넷 표준 인증 방식

  • 토큰 인증 방식에서 가장 범용적으로 사용

  • Json 포맷의 토큰 정보를 인코딩 후, 인코딩된 토큰 정보를 Secret Key로 서명한 메세지를 Web Token으로써 인증 과정에 사용

JWT의 종류

  • 액세스 토큰

  • 리프레시 토큰

액세스 토큰 (Access Token)

  • 보호된 정보들에 접근할 수 있는 권한 부여에 사용

  • 실제 권한을 얻는데 사용

  • 짧은 유효 기간

    • 탈취 당하는 경우 기한이 금방 만료되어 사용하기 힘들다.

리프레쉬 토큰 (Refresh Token)

  • 긴 유효 기간

    • Refresh Token의 유효 기간동안 새로운 Access Token을 재발급받을 수 있음
    • Access Token을 재발급하여 새롭게 인증할 필요가 없으므로 사용자의 편의성이 증가
  • 탈취 당하지 않도록 주의가 필요

    • 편의성보다 정보가 중요하면, 사용하지 않는 것을 권장

JWT 구조

JWT는 Header, Payload, Signature로 이루어져있다.

  • 토큰의 타입

  • 서명 생성에 사용하는 알고리즘

{
  "alg": "HS512",
  "typ": "JWT"
}

Payload

  • Claim이라는 사용자나 토큰에 대한 속성을 key-value의 형태로 저장

  • iss, sub, aud, exp, nbf, iat, jti와 같은 표준 스펙이 있지만 꼭 포함해야 하는 것은 아님

  • 암호화가 걸려있지 않기에 민감한 정보를 담지 않는 것이 중요

{
  "sub": "information",
  "iat": 151623391,
  "exp": 151623391
}

Signature

  • 원하는 비밀 키와 Header에서 지정한 알고리즘으로 Header와 Payload에 대해서 단방향 암호화를 수행

  • 암호화된 메세지로 토큰의 위변조 유무를 검증

  • Signature의 구조

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  
your-256-bit-secret

) secret base64 encoded

장점

  • 상태를 유지하지 않고(Stateless), 확장에 용이한 (Scalable) 애플리케이션 구현에 용이

  • 토큰이 만료되기 전까지 한 번의 인증만 수행

  • 인증을 담당하는 시스템을 다른 플랫폼으로 분리하기에 용이

단점

  • 쿠키, 세션과는 달리 base64 인코딩을 통한 정보를 전달하므로 전달량이 많고, 네트워크에 부하가 갈 수 있다.

  • 토큰이 탈취당하는 경우 만료될 때까지 대처할 수 없다.

  • Payload에는 암호화가 되어있지 않아 민감한 정보를 저장할 수 없다.

JWT 토큰 생성

Gradle 설정

JWT 생성 및 검증 테스트를 수행하는데 필요한 라이브러리

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

JWT 생성 기능 구현

public class JwtTokenizer {
    public String encodeBase64SecretKey(String secretKey){
        return Encoders.BASE64.encode(secretKey.getBytes(StandardCharsets.UTF_8));
    }
    
    public String generateAccessToken(Map<String, Object> claims,
                                      String subject,
                                      Date expiration,
                                      String base64EncodedSecretKey){
        Key key = getKeyEncodedBase64(base64EncodedSecretKey);
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(Calendar.getInstance().getTime())
                .setExpiration(expiration)
                .signWith(key)
                .compact();
    }

    public String generateRefreshToken(String subject,
                                       Date expiration,
                                       String base64EncodedSecretKey){
        Key key = getKeyEncodedBase64(base64EncodedSecretKey);
        return Jwts.builder()
                .setSubject(subject)
                .setIssuedAt(Calendar.getInstance().getTime())
                .setExpiration(expiration)
                .signWith(key)
                .compact();
    }

    private Key getKeyEncodedBase64(String base64EncodedSecretKey){
        byte[] keyBytes = Decoders.BASE64.decode(base64EncodedSecretKey);
        return Keys.hmacShaKeyFor(keyBytes);
    }
}
  • encodeBase64SecretKey(): Plain Text 형태인 key를 Base64 형식의 문자열로 인코딩

    • Plain Text 자체를 Secret Key로 사용하는 것은 권장되지 않는 방법
  • getKeyEncodedBase64(): JWT의 서명에 사용할 Key를 생성

    • Decoders.BASE64.decode(): Base64 형식으로 인코딩된 Secret Key를 디코딩하여 byte array 형태로 반환
    • Keys.hmacShaKeyFor(): key byte array를 기반으로 적절한 HMAC 알고리즘을 적용한 Key 객체를 생성
  • generateAccessToken(): JWT의 AccessToken을 생성

    • setClaims(): 인증된 사용자와 관련된 정보를 설정

    • setSubject(): JWT의 제목을 설정

    • setIssuedAt(): JWT의 발행 일자를 설정

    • setExpiration(): JWT의 만료일시를 설정

    • signWith(): 서명을 위한 Key 객체를 설정

    • compact(): JWT를 생성하고 직렬화

profile
백엔드를 지망하는 개발자

0개의 댓글