[Spring Security] JWT 토큰이란?

WOOK JONG KIM·2022년 12월 9일
0

패캠_java&Spring

목록 보기
94/103
post-thumbnail

서버에서 인증된 사용자가 인증을 유지해주는 방법으로 보통은 세션을 사용함
-> 서버 세션을 사용하면 인증된 사용자는 매우 편리하게 서비스를 이용할 수 있고, 대부분의 웹애플리케이션 서버가 세션을 지원하기 때문에 편리

하지만 세션을 이용할 때
1. 서버를 여러대 둘 경우 (scale out),
2. 같은 사용자가 서로 다른 도메인의 데이터를 요청할 경우, (SSO)에는 세션을 유지하기 위한 비용이 매우 커짐
-> 이 때는 서버에 사용자 정보를 저장하는 대신 클라이언트에 사용자 정보를 내려주고,서버는 토큰의 사용자 정보를 모든 요청에서 확인하고 서비스를 해주는 방식(sessionless)일 때, JWT 토큰이 유용하게 사용됩

JWT 토큰의 구조 : header + body + signature

JWT 스펙에서 지정한 claim

  • iss : Issuer 토큰을 발행한 사람(단체,사이트)이 누구인지
  • sub : Subject 무엇에 관한 토큰인지
  • aud : Audience 누구를 대상으로 한 토큰인지
  • exp : Expiration 토큰의 만료 시간은 언제인지
  • nbf : Not Before 토큰이 언제부터 유효한지
  • iat : Issued At 토큰 발행 시간
  • jti : JWT ID : 토큰 자체의 아이디(일련번호?)
  • 그 밖에 인증에 필요하거나 대상서버에서 필요로 하는 데이터

토큰에 넣는 내용

  • 일반적으로는 인증에 필요한 최소한의 데이터 넣음
  • 비밀번호나 전화번호등을 넣는 것은 안전X
  • 이 토큰은 언제든 공개할 수 있는 정보를 넣는 것이 좋다
    -> 서버에서 인증된 키가 아니라도 언제든 서버는 이 토큰을 열어서 그 안에 어떤 Claim 이 있는지를 볼 수 있습니다.

토큰 관리

이론적으로는 토큰을 클라이언트가 관리
-> 하지만, 실제로 서버는 사용자 정보 캐싱이나 토큰의 유효성 평가, 혹은 refresh 토큰 정책을 위해 서버에 토큰을 관리하기도 함
이 경우, 토큰과 사용자 정보를 관리하는 방법으로 다음과 같은 방법들을 사용하기도 합니다

  1. redis, hazelcast
  2. db 저장

테스트코드 예시


public class JWTSimpleTest {

    private void printToken(String token){
        String[] tokens = token.split("\\.");

        // 헤더 및 바디 프린트
        System.out.println("header : " + new String(Base64.getDecoder().decode(tokens[0])));
        System.out.println("Body : " + new String(Base64.getDecoder().decode(tokens[1])));
    }


    @DisplayName("1. jjwt 를 이용한 토큰 테스트")
    @Test
    void test_1(){
        String okta_token = Jwts.builder().addClaims(
                Map.of("name", "wookjong", "price", 3000)
                ).signWith(SignatureAlgorithm.HS256, "wookjong")
                .compact();
        System.out.println(okta_token);
        printToken(okta_token);

        // 검증
        Jws<Claims> tokeninfo = Jwts.parser().setSigningKey("wookjong").parseClaimsJws(okta_token);
        System.out.println(tokeninfo);
    }


    @DisplayName("2. java-jwt 를 이용한 토큰 테스트")
    @Test
    void test_2() {

        // 같은 키를 사용해서 검증하게 하는 법
        // 클라이언트가 누구든지 간에 알고리즘과 키값이 서로 일치 한다면 누구든 검증가능한 토큰임
        byte[] SEC_KEY = DatatypeConverter.parseBase64Binary("wookjong");


        String oauth0_token = JWT.create().withClaim("name", "wookjong").withClaim("price", 3000)
                .sign(Algorithm.HMAC256(SEC_KEY));
        System.out.println(oauth0_token);
        printToken(oauth0_token);

        DecodedJWT verified = JWT.require(Algorithm.HMAC256(SEC_KEY)).build().verify(oauth0_token);
        System.out.println(verified.getClaims());

        // 원래라면 내부적인 key값이 달라 검증 불가
        Jws<Claims> tokenInfo = Jwts.parser().setSigningKey(SEC_KEY).parseClaimsJws(oauth0_token);
        System.out.println(tokenInfo);
    }

    @DisplayName("3. 만료 시간 테스트")
    @Test
    void test3() throws InterruptedException {

        final Algorithm AL = Algorithm.HMAC256("wookjong");

        // 유효 시간 3초
        String token = JWT.create().withSubject("a1234")
                .withNotBefore(new Date(System.currentTimeMillis() + 1000))
                .withExpiresAt(new Date(System.currentTimeMillis() + 3000))
                .sign(AL);

        // 이미 만료가 되버림
//        Thread.sleep(2000);
        try {
            DecodedJWT verify = JWT.require(AL).build().verify(token);
            System.out.println(verify.getClaims());
        } catch(Exception ex){
            // 유효하지 않은 경우에도 토큰을 열어보는 경우
            System.out.println("유효하지 않은 토큰입니다...");
            DecodedJWT decode = JWT.decode(token); // jjwt는 decode 제공 X
            System.out.println(decode.getClaims());
        }
    }

}
profile
Journey for Backend Developer

0개의 댓글