프로젝트 설정
Spring-Auth 프로젝트 열기
build.gradle 열기 -> dependencies에 아래의 코드 추가
// JWT
compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'
코끼리 새로고침 누르기
application.properties로 이동
아래의 코드 추가
jwt.secret.key=7Iqk7YyM66W07YOA7L2U65Sp7YG065+9U3ByaW5n6rCV7J2Y7Yqc7YSw7LWc7JuQ67mI7J6F64uI64ukLg==
jwt 패키지 추가 -> JwtUtil 클래스 생성
JwtUtil 만들기
Util 클래스란 특정 매개 변수(파라미터)에 대한 작업을 수행하는 메서드들이 존재하는 클래스를 뜻한다.
쉽게 설명하자면 다른 객체에 의존하지 않고 하나의 모듈로서 동작하는 클래스라고 생각하면 좋다.
우리는 JWT 관련 기능들을 가진 JwtUtil이라는 클래스를 만들어 JWT 관련 기능을 수행시킬 예정이다.
<JWT 관련 기능>
1. JWT 생성
2. 생성된 JWT를 Cookie에 저장
3. Cookie에 들어있던 JWT 토큰을 Substring
4. JWT 검증
5. JWT에서 사용자 정보 가져오기
JwtUtil 클래스를 이렇게 작성
package com.sparta.springauth.jwt;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Base64;
@Component
public class JwtUtil {
// Header KEY 값
public static final String AUTHORIZATION_HEADER = "Authorization";
// 사용자 권한 값의 KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token 식별자
public static final String BEARER_PREFIX = "Bearer ";
// 토큰 만료시간
private final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
@Value("${jwt.secret.key}") // Base64 Encode 한 SecretKey
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 로그 설정
public static final Logger logger = LoggerFactory.getLogger("JWT 관련 로그");
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
}
주석으로 Header KEY 값으로 되어있는 부부은 Cookie의 name 값이라고 보면 된다.
사용자 권한 값의 KEY : 유저들 마다의 권한에 대한 정보를 담는 부분이다. (관리자, 일반 유저 등등의 구분)
Token 식별자 : 우리가 만들 Token 앞에 붙일 용어 꼭 붙일 필요는 없지만 규칙같은거라서 붙여주면 좋다. Value 앞에 붙는다. 구분을 위해서 마지막 한 칸 띄어준다.
토큰 만료 시간 : 토큰이 유지가능한 시간 기준은 ms 단위이다. 현재는 60초 * 60과 마찬가지이다.
@Value의 괄호 안에 "${ }" 형식으로 넣는다. jwt.secret.key는 아까 application.properties 에서 설정한 값이다.
jwt.secret.key=7Iqk7YyM66W07YOA7L2U65Sp7YG065+9U3ByaW5n6rCV7J2Y7Yqc7YSw7LWc7JuQ67mI7J6F64uI64ukLg==
그리고 이 값을 secretKey 값에 담아준다.
27번째 줄은 key에 security key를 담아서 JWT를 암호화하거나 복화해서 검증할 때 사용한다.
28번째 줄은 암호화 알고리즘을 HS256을 사용한다는 것이다. SignatureAlgorithm에 들어가보면 enum으로 되어있다. 그 중에서 HS256을 선택했다고 보면 된다.
31번째 줄은 로그 설정이다. 이런걸 로깅이라고하며 로깅이란 애플리케이션이 동작하는 동안 프로젝트의 상태나 동작 정보를 시간순으로 기록하는것을 의미한다. "JWT 관련 로그" 라고 되어 있는것은 로그의 명칭이다.
33번째 줄의 @PostConstruct는 딱 한번만 호출하면 되는 메서드를 여러번 호출하는것을 예방하는 애노테이션이다. 이 클래스의 객체를 생성한 뒤 이 코드가 실행이 된다.
내용물을 설명하자면 key에 값이 들어가는데 security 값이 base64로 인코딩된 값이다. 해당 secret 값을 사용하려면 decode를 한번 해줘야 해서 한번 해준다. 반환 값이 byte[] 타입이므로 byte[]로 받는다. 그 다음에 Keys 라는 클래스에 hmacShaKeyFor 메서드가 있는데 그 인자로 아까 디코드한 bytes를 넣어주면 변환이 일어난 다음에 key값에 우리가 사용할 security 값이 담긴다.
실제로 사용하기 전에 하나 할것이 있다.
사용자의 권한을 관리하는 enum 클래스를 하나 만들것이다.
entity 패키지를 만들고 UserRoleEnum Enum 클래스를 만든다.
package com.sparta.springauth.entity;
public enum UserRoleEnum {
USER(Authority.USER), // 사용자 권한
ADMIN(Authority.ADMIN); // 관리자 권한
private final String authority;
UserRoleEnum(String authority) {
this.authority = authority;
}
public String getAuthority() {
return this.authority;
}
public static class Authority {
public static final String USER = "ROLE_USER";
public static final String ADMIN = "ROLE_ADMIN";
}
}
이렇게 만들고 다시 JwtUtil 클래스로 이동
토큰 생성
이 코드는 받은 key를 암호화해서 여러 정보와 함께 토큰을 만든것이다.
현재 Date가 import가 되어있지 않은 상태이다. 이는 Java.util에서 import 해준다.
48번줄의 .claim은 KEY, Value 값으로 넣어준다. (만약 JWT에 등록할 정보 필드가 더 많다면 .claim을 여러 번 사용해서 계속 추가가 가능하다)
49번 줄의 setExpiration 함수에서 사용된 date.getTime()은 현재 시간을 의미한다. 아까 위에서 만든 60분의 시간을 추가시켰다.
// JWT Cookie 에 저장
public void addJwtToCookie(String token, HttpServletResponse res) {
try {
token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서 encoding 진행
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
cookie.setPath("/");
// Response 객체에 Cookie 추가
res.addCookie(cookie);
} catch (UnsupportedEncodingException e) {
logger.error(e.getMessage());
}
}
63번째 줄 : 쿠키에는 공백이 들어갈 수 없으므로 인코딩을 해준다.
// JWT 토큰 substring
public String substringToken(String tokenValue) {
if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
return tokenValue.substring(7);
}
logger.error("Not Found Token");
throw new NullPointerException("Not Found Token");
}
현재 기능은 토큰에서 Token 식별자를 Value에서 떼어내는 기능을 한다.
StringUtils.hasText()는 현재 인수의 텍스트가 공백인지 혹은 Null인지 확인할 수 있다.
tokenValue.startsWith(BEARER_PREFIX))는 tokenValue의 시작이 BEARER_PREFIX로 시작하는지 확인하는 메서드다.
그리고 substring(7)은 7글자 떼어내는 함수이다. 7글자를 떼어내는 이유는 BERER_PREFIX를 떼어내야 하는데, 이 값이
"Bearer " 공백포함 7글자이기 때문이다.
// 토큰 검증
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException | SignatureException e) {
logger.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
} catch (ExpiredJwtException e) {
logger.error("Expired JWT token, 만료된 JWT token 입니다.");
} catch (UnsupportedJwtException e) {
logger.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
} catch (IllegalArgumentException e) {
logger.error("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
}
return false;
}
substring 을 이용해 순수한 token값을 받아온 후 검증한다
// 토큰에서 사용자 정보 가져오기
public Claims getUserInfoFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
JWT 테스트
AuthController 클래스로 이동한다. 그리고 아래의 코드를 복붙한다.
@GetMapping("/create-jwt")
public String createJwt(HttpServletResponse res) {
// Jwt 생성
String token = jwtUtil.createToken("Robbie", UserRoleEnum.USER);
// Jwt 쿠키 저장
jwtUtil.addJwtToCookie(token, res);
return "createJwt : " + token;
}
@GetMapping("/get-jwt")
public String getJwt(@CookieValue(JwtUtil.AUTHORIZATION_HEADER) String tokenValue) {
// JWT 토큰 substring
String token = jwtUtil.substringToken(tokenValue);
// 토큰 검증
if(!jwtUtil.validateToken(token)){
throw new IllegalArgumentException("Token Error");
}
// 토큰에서 사용자 정보 가져오기
Claims info = jwtUtil.getUserInfoFromToken(token);
// 사용자 username
String username = info.getSubject();
System.out.println("username = " + username);
// 사용자 권한
String authority = (String) info.get(JwtUtil.AUTHORIZATION_KEY);
System.out.println("authority = " + authority);
return "getJwt : " + username + ", " + authority;
}
일단 jwtUtil이 오류가 날것이다. 우리는 JwtUtil 클래스를 Bean으로 등록했었으니 이걸 주입받으면 해결된다.
생성자를 통해 주입받은 모습이다.
@CookieValue( )은 쿠키의 이름을 받아서 일치하는 이름의 쿠키의 값을 받아서 tokenValue에 넣는 애노테이션이라고 배웠었다.
info.get( ) : 괄호 안에 JWT의 json key를 문자열로 넣어주면 key에 해당하는 json value를 반환해주는 함수이다.
'Spring 숙련주차 > 1주차' 카테고리의 다른 글
8. 로그인 구현 JWT (0) | 2024.08.26 |
---|---|
7. 회원가입 구현 (0) | 2024.08.23 |
5. JWT란 무엇일까? (0) | 2024.08.22 |
4. 쿠키와 세션이란 무엇일까? (0) | 2024.08.21 |
3. 인증과 인가란 무엇일까? (0) | 2024.08.21 |