본문 바로가기
공부/프로그래밍

[springboot, jwt] JWT 사용하기

by demonic_ 2021. 6. 23.
반응형

공식 페이지

https://jwt.io/

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

 

JWT란?

JSON Web Token의 약자로 웹표준(RFC 7519)으로 두 개체에서 JSON객체를 사용하여 가볍고 자가수용적(self-contained)인 방식으로 정보를 안정성 있게 전달.

 

자가수용적(self-contained) 이란?

필요한 모든 정보를 자체적으로 가지고 있음을 의미.

 

사용이유

장점

인증서버, 데이터 스토어 등 의존성 없음.

시스템 수평 확장 유리

Base64 URL SAFE ENCODING 으로 URL, Cookie, Header 모두 사용 가능

 

단점

Payload의 정보가 많아지면 네트워크 사용량 증가

클라이언트에서 저장하기 때문에 서버에서 클라이언트 토큰을 조작할 수 없다

 

 

JWT 생김새

 

 

빨간색 부분은 헤더, 보라색 부분은 내용, 그리고 푸른색 부분은 서명정보를 의미한다

 

헤더(header)

유형(typ)과 알고리즘(alg) 정보를 담음. 보통 HMAC SHA256을 사용.

유형(typ)은 토큰의 타입을 지정하며 여기서는 JWT로 지정.

 

정보(payload)

name / value 한쌍으로 이루어져 있으며 토큰에는 여러개의 클레임을 넣을 수 있음.

시스템에서 실제로 사용될 정보에 대한 내용들을 담음

iss: 토큰

sub: 토큰 제목 (subject)

exp: 만료시간 (expiration)

iat: 토큰 발급 시간 (issued at)

aud: 토큰 대상자(audience)

 

유효성 검증(Signature)

토큰의 유효성 검증을 위한 문자열

 

 

Spring 에서 사용하기

Dependency 추가

...
    implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
    implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
    implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
...

 

JWT 관리 매니저

JwtManager.java 생성

public class JwtManager {
    private String secretKey;
    private long TOKEN_VALIDATiON_SECOND = 60;

    public JwtManager(String secretKey, long TOKEN_VALIDATiON_SECOND) {
        this.secretKey = secretKey;
        this.TOKEN_VALIDATiON_SECOND = TOKEN_VALIDATiON_SECOND;
    }

    // secretKey 로드
    private Key getSigninKey() {
        byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
        return Keys.hmacShaKeyFor(keyBytes);
    }

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

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + (TOKEN_VALIDATiON_SECOND * 1000)))
                .signWith(getSigninKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    // 토큰 정보 리턴
    public TokenInfo getTokenInfo(String token) {
        Claims body = getClaims(token);
        Set<String> keySet = body.keySet();
        for (String s : keySet) {
            System.out.println("s = " + s);
        }

        String username = body.get("username", String.class);
        Date issuedAt = body.getIssuedAt();
        Date expiration = body.getExpiration();
        return new TokenInfo(username, issuedAt, expiration);
    }

    // 토큰정보 해석
    private Claims getClaims(String token) {
        Claims body = Jwts.parserBuilder()
                .setSigningKey(getSigninKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
        return body;
    }


    @Getter
    public class TokenInfo {
        private String username;
        private Date issuedAt;
        private Date expire;

        public TokenInfo(String username, Date issuedAt, Date expire) {
            this.username = username;
            this.issuedAt = issuedAt;
            this.expire = expire;
        }
    }
}

 

Claims 내 body 메서드를 보면 다음의 get메서드들이 보인다.

Payload 에 소개했던 내용을 확인할 수 있다.

 

이번 코드에서는 생성시간(iat), 만료시간(exp), 그리고 username 이라는 커스텀 키만 쓰고 있다.

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

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + (TOKEN_VALIDATiON_SECOND * 1000)))
                .signWith(getSigninKey(), SignatureAlgorithm.HS256)
                .compact();
    }
...

 

Bean으로 등록

@Configuration
public class JwtConfig {
		private String jwtSecret = "dGVzdC1qd3Qtc2FtcGxlLXNwcmluZ2Jvb3Q=";

    // 유효시간을 86400 초로 계산
		private long tokenValidityInSeconds = 86400;
    
    @Bean
    public JwtManager jwtManager() {
        return new JwtManager(jwtSecret, tokenValidityInSeconds);
    }
}

 

호출예제(Controller 이용)

@RestController
@RequiredArgsConstructor
public class MainController {
    private final JwtManager jwtManager;

    /**
     * JWT 발급
     */
    @GetMapping("/api/jwt/create/{username}")
    public ResponseJwtCreate jwtCreate(@PathVariable String username) {
        String token = jwtManager.generateToken(username);
        return new ResponseJwtCreate(token);
    }

    /**
     * JWT 토큰 해석
     * @return
     */
    @GetMapping("/api/jwt/info/{token}")
    public JwtManager.TokenInfo jwtInfo(@PathVariable String token) {
        JwtManager.TokenInfo tokenInfo = jwtManager.getTokenInfo(token);
        return tokenInfo;
    }
}

 

 

토큰발급 결과

http://localhost:8080/api/jwt/create/aaron

{
  token: "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFhcm9uIiwiaWF0IjoxNjI0MzMyMTkyLCJleHAiOjE2MjQ0MTg1OTJ9.2IuJipJLZbbspOeX8Hay7BHvUKqBlw4YeDMUDpLFHpY"
}

 

토큰을 이용한 조회

http://localhost:8080/api/jwt/info/{token값}

{
  username: "aaron",
  issuedAt: "2021-06-22T03:23:12.000+00:00",
  expire: "2021-06-23T03:23:12.000+00:00",
}

 

만료시간이 지난경우

서버에서 다음과 같은 에러 발생(ExpiredJwtException.class)

token = eyJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFhcm9uIiwiaWF0IjoxNjI0MzI3MjY3LCJleHAiOjE2MjQzMjczNTN9.cPfY75969d841v75xRRAx0cDb0WPxbSyTF-O-_6kj7I

io.jsonwebtoken.ExpiredJwtException: JWT expired at 2021-06-22T02:02:33Z. Current time: 2021-06-22T03:25:21Z, a difference of 4968117 milliseconds.  Allowed clock skew: 0 milliseconds.

 

 

끝.

 

 

github:

https://github.com/lemontia/java-simple-jwt-manager/tree/main

 

lemontia/java-simple-jwt-manager

Contribute to lemontia/java-simple-jwt-manager development by creating an account on GitHub.

github.com

 

반응형

댓글