JWT를 Spring에서 어떻게 사용하는지 알아보자
일단 JwtTokenizer클래스를 만들고 @Component를 사용해서 빈으로 등록하자
메소드를 중심으로 설명해 보겠다.
https://naturecancoding.tistory.com/109
JWT가 뭔지 궁금한 분들은 기초를 보고오자
1. 맴버 변수
private final byte[] accessSecret; //JWT 서명을 위한 비밀 키 엑세스 토큰에 사용
private final byte[] refreshSecret; //리프레시 토큰에 사용
//위 2개는 문자열로부터 바이트 배열로 변환됨
public final static Long ACCESS_TOKEN_EXPIRE_COUNT = 30 * 60 * 1000L;//30분
public final static Long REFRESH_TOKEN_EXPIRE_COUNT = 7 * 24 * 60 * 60 * 1000L;//7일
메소드에쓸 변수들이다. Refresh가 Access보다 큰 이유는 다시 로그인 할 필요가 7일동안 없게 하기 위해서 이다.
1-1. AccessToken
- 역할 :
클라이언트가 보호된 자원에 접근할 수 있도록 인증을 할 때 사용
액세스 토큰은 요청마다 전송됨으로, 자주 사용된다. - 수명 :
비교적 짧은 시간 - 보안 이유 :
액세스 토큰이 유출될 경우 공격자는 이 토큰을 사용해서 인증된 사용자인척 할 수 있는데 짧은 수명을 줌으로써 공격자가 공격을 할수 있는 시간을 줄인다.
1-2. Refresh Token
- 역할 :
액세스 토큰이 만료될 경우 클라이언트가 새로운 액세스 토큰을 발급해줄 때 사용한다. - 수명 :
비교적 긴 시간 - 보안 이유 :
액세스 토큰을 발급받을 때만 사용되기 때문에. 상대적으로 안전한 환경에서 사용된다.
길게 설정된 수명으로 인해서 사용자가 자주 로그인 안해도 되도록 한다.
2. JwtTokenizer
public JwtTokenizer(@Value("${jwt.accessSecretKey}") String accessSecret, @Value("${jwt.refreshSecretKey}") String refreshSecret){
this.accessSecret = accessSecret.getBytes(StandardCharsets.UTF_8);
this.refreshSecret = refreshSecret.getBytes(StandardCharsets.UTF_8);
}
@Value를 사용해서 yml파일 혹은 properties파일에서 비밀 키 값을 주입받는다.
이 주입된 비밀 키 값을 byte 배열로 변환해서 저장한다.
3. getSigningKey + createToken + createAccess, RefreshToken
public static Key getSigningKey(byte[] secretKey) {
return Keys.hmacShaKeyFor(secretKey);
}
비밀 키를 HMAC SHA 알고리즘에서 사용 가능한 키로 변환한다.
HMAC SHA알고리즘이란 서명을 생성하고 검증하는 방식을 뜻하며 JWT는 이 알고리즘을 따른다고 할 수 있다.
다음은 사용자의 정보를 가지고 토큰을 생성하는 메소드이다.
private String createToken(Long id, String email, String name, String username, List<String> roles, Long expire, byte[] secretKey)
- id: 사용자 ID
- email: 사용자 이메일
- name: 사용자 이름
- username: 사용자 이름
- roles: 사용자 역할 리스트
- expire: 토큰의 만료 시간 (밀리초 단위)
- secretKey: JWT 서명을 위한 비밀 키 (바이트 배열)
Claims claims = Jwts.claims().setSubject(email);
Claims을 생성하는데 이때 클레임의 주제를 email로 설정한다. 사용자를 식별하는데 사용한다.
claims.put("roles", roles);
claims.put("userId", id);
claims.put("name", name);
claims.put("username", username);
JWT의 claim들에 key-value쌍으로 값을 넣는다.
name과 uesrname이 있는 이유는 username은 springsecurity에서 사용하는 사용자 식별자이다.
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(new Date())
.setExpiration(new Date(new Date().getTime() + expire))
.signWith(getSigningKey(secretKey))
.compact();
1. Jwts.Builder()
JWT 빌더 객체를 생성
2. setClaims(claims)
위에서 설정한 클레임을 JWT에 설정
3. setIssuedAt(new Date())
JWT 발행 시간을 현재 시간으로 설정함
4. setExpiration(new Date(new Date().getTime() + expire))
JWT만료 시간을 설정함 현재 시간에 expire 밀리초를 더한 시간이 만료 시간이 됨
5. compact()
JWT를 생성하고 문자열로 반환
아래와 같이 사용하게 된다.
public String createAccessToken(Long id, String email, String name, String username, List<String> roles) {
return createToken(id, email, name, username, roles, ACCESS_TOKEN_EXPIRE_COUNT, accessSecret);
}
public String createRefreshToken(Long id, String email, String name, String username, List<String> roles) {
return createToken(id, email, name, username, roles, REFRESH_TOKEN_EXPIRE_COUNT, refreshSecret);
}
4. parseToken + getUserIdFromToken + parseAccess, RefreshToken
public Claims parseToken(String token, byte[] secretKey) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey(secretKey))
.build()
.parseClaimsJws(token)
.getBody();
}
1. Jwts.parserBuilder()
jwt의 파서 빌더를 초기화함. 이 빌더는 JWT 파싱 및 검증을 설정하는 데 사용됨
파싱이라는 것은 문자열 형태 데이터를 특정 규칙 혹은 문법에 따라 변환하는 과정을 의미
2. setSigningKey(getSigningKey(secretKey))
getSigningKey 비밀 키를 서명 검증에 사용할 키 객체로 변환함
setSigningKey 키 객체를 JWT 파서에 설정
3. build()
파서 빌더를 사용해서 JWT 파서 생성
4. parseClaimsJws(token)
JWT 토큰을 파싱하고 서명을 검증한다. 전달된 토큰의 서명이 설정된 비밀 키로 검증된다.
이때 서명이 유효하면 토큰의 payload안에 있는 claim들을 추출한다.
5. getBody
검증된 JWT 토큰의 claim들을 반환한다.
토큰으로 부터 User의 Id를 가져온다.
public Long getUserIdFromToken(String token) {
String[] tokenArr = token.split(" ");
token = tokenArr[1];
Claims claims = parseToken(token, accessSecret);
return Long.valueOf((Integer)claims.get("userId"));
}
JWT문자열을 받는다.
전달된 문자열을 공백 문자로 분리하고 2번째 요소를 token에 저장한다.
parseToken을 호출해서 토큰을 파싱하고 claim들을 추출한다. accessSecret을 사용해서 서명을 검증한다.
userid를 LongType으로 가져와서 반환한다.
public Claims parseAccessToken(String accessToken) {
return parseToken(accessToken, accessSecret);
}
public Claims parseRefreshToken(String refreshToken) {
return parseToken(refreshToken, refreshSecret);
}
각각 어떤 키를 통해서 토큰을 파싱할지 정한다. 그리고 claim들을 반환한다.
Coding, Software, Computer Science 내가 공부한 것들 잘 이해했는지, 설명할 수 있는지 적는 공간