공식 페이지
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
'공부 > 프로그래밍' 카테고리의 다른 글
[postman] 결과값 변수에 자동 설정하기 (0) | 2021.07.02 |
---|---|
[java] 요청한 IP주소 받기(nginx proxy 환경, AWS ELB 등) (0) | 2021.07.02 |
[airflow] mysql 연동으로 수행할때 에러 (Reason: image not found) (0) | 2021.06.19 |
[react] redux 대신 SWR으로 Local 상태 활용하기 (0) | 2021.06.18 |
[spring cloud] eureka 설정 중 에러(Network level connection to peer localhost; retrying after delay) (0) | 2021.06.10 |
댓글