728x90
[JWT를 활용하여 인증과 토큰을 관리하는 서비스 구현]
1. build.gradle 의존성 추가
/* jwt */
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
implementation 'com.auth0:java-jwt:3.13.0'
2. entity, repository 수정
1) entity 수정
//jwt refreshToken 추가
@Column(length = 1000)
private String refreshToken;
public void updateRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public void destroyRefreshToken() {
this.refreshToken = null;
}
2) repository 수정
//refreshToken을 통해 유저를 조회하는 기능을 추가
Optional<UserEntity> findByRefreshToken(String refreshToken);
3.JwtService
1) application-jwt.yml
jwt:
secret: 시크릿 키
access:
expiration: 80 //80초
header: Authorization
refresh:
expiration: 90 //90초
header: Authorization-refresh
- jwt.secret: JWT 서명에 사용되는 비밀 키로, 이 키는 JWT를 생성할 때 서명에 사용되며, 토큰의 유효성을 검증할 때도 필요하다. 키 생성은 아래와 같이 랜덤키 생성하여 넣어줬다.
//32바이트 랜덤 키 생성
openssl rand -hex 32
//64바이트 랜덤 키 생성
openssl rand -hex 64
- jwt.access.expiration: 액세스 토큰의 만료 시간(초)이다.
- jwt.access.header: 액세스 토큰이 포함될 HTTP 헤더의 이름이다. 클라이언트는 Authorization 헤더에 액세스 토큰을 전달해야 한다.
- jwt.refresh.expiration: 리프레시 토큰의 만료 시간(초)이다.
- jwt.refresh.header: 리프레시 토큰이 포함될 HTTP 헤더의 이름이다. 클라이언트는 Authorization-refresh 헤더에 리프레시 토큰을 전달해야 한다.
2) JwtService.java 인터페이스 구현
JwtService 인터페이스는 JWT 관련 주요 기능을 선언한 인터페이스이다. 이 인터페이스의 목적은 JWT 액세스 토큰과 리프레시 토큰을 생성, 검증, 갱신, 파기하는 기능을 정의하는 것이다.
public interface JwtService {
String createAccessToken(String userEmail)
String createRefreshToken();
void updateRefreshToken(String userEmail, String refreshToken);
void destoryRefreshToken(String userEmail);
void sendAccessAndRefreshToken(HttpServletResponse response, String accessToken, String refreshToken);
void sendAccessToken(HttpServletResponse response, String accessToken);
Optional<String> extractAccessToken(HttpServletRequest request);
Optional<String> extractRefreshToken(HttpServletRequest request);
Optional<String> extractEmail(String accessToken);
void setAccessTokenHeader(HttpServletResponse response, String accessToken);
void setRefreshTokenHeader(HttpServletResponse response, String refreshToken);
boolean isTokenValue(String token);
}
- createAccessToken(String userEmail): 사용자의 이메일을 기반으로 액세스 토큰을 생성한다.
- createRefreshToken(): 리프레시 토큰을 생성한다.
- updateRefreshToken(String userEmail, String refreshToken): 특정 사용자의 리프레시 토큰을 업데이트한다.
- destoryRefreshToken(String userEmail): 특정 사용자의 리프레시 토큰을 삭제한다.
- sendAccessAndRefreshToken(HttpServletResponse response, String accessToken, String refreshToken): 응답에 액세스 토큰과 리프레시 토큰을 헤더로 포함시킨다.
- sendAccessToken(HttpServletResponse response, String accessToken): 응답에 액세스 토큰을 헤더로 포함시킨다.
- extractAccessToken(HttpServletRequest request): 요청에서 액세스 토큰을 추출한다.
- extractRefreshToken(HttpServletRequest request): 요청에서 리프레시 토큰을 추출한다.
- extractEmail(String accessToken): 액세스 토큰에서 이메일 정보를 추출한다.
- setAccessTokenHeader(HttpServletResponse response, String accessToken): 응답에 액세스 토큰을 헤더로 설정한다.
- setRefreshTokenHeader(HttpServletResponse response, String refreshToken): 응답에 리프레시 토큰을 헤더로 설정한다.
- isTokenValue(String token): 토큰이 유효한지 검증한다.
4) JwtServiceImpl.java 구현체 구현
JwtServiceImpl 클래스는 JwtService 인터페이스의 구현체로, JWT 생성, 검증, 토큰 갱신 등을 담당한다.
[필드]
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.accsee.expiration}")
private long accessTokenValidityInSeconds;
@Value("${jwt.refresh.expiration}")
private long refreshTokenValidityInSeconds;
@Value("${jwt.accsee.header}")
private String accessHeader;
@Value("${jwt.refresh.header}")
private String refreshHeader;
private static final String ACCESS_TOKEN_SUBJECT = "AccessToken";
private static final String REFRESH_TOKEN_SUBJECT = "RefreshToken";
private static final String USERNAME_CLAIM = "email";
private static final String BEARER = "Bearer";
private final UserRepository userRepository;
- secret: application-jwt.yml에서 정의된 JWT 비밀
- accessTokenValidityInSeconds: application-jwt.yml에서 설정된 액세스 토큰의 유효 기간(초)
- refreshTokenValidityInSeconds: application-jwt.yml에서 설정된 리프레시 토큰의 유효 기간(초)
- accessHeader, refreshHeader: 각각 액세스 토큰과 리프레시 토큰을 헤더에서 추출하기 위한 설정값
[주요 메서드]
@Override
public String createAccessToken(String userEmail) {
return JWT.create()
.withSubject(ACCESS_TOKEN_SUBJECT)
.withExpiresAt(new Date(System.currentTimeMillis() + accessTokenValidityInSeconds * 1000))
.withClaim(USERNAME_CLAIM, userEmail)
.sign(Algorithm.HMAC512(secret));
}
@Override
public String createRefreshToken() {
return JWT.create()
.withSubject(REFRESH_TOKEN_SUBJECT)
.withExpiresAt(new Date(System.currentTimeMillis() + refreshTokenValidityInSeconds * 1000))
.sign(Algorithm.HMAC512(secret));
}
@Override
public void updateRefreshToken(String userEmail, String refreshToken) {
userRepository.findByUserEmail(userEmail)
.ifPresentOrElse(users -> users.updateRefreshToken(refreshToken),
() -> new Exception("updateRefreshToken() 에러 - 조회 실패"));
}
@Override
public void destoryRefreshToken(String userEmail) {
userRepository.findByUserEmail(userEmail)
.ifPresentOrElse(users -> users.destroyRefreshToken(),
() -> new Exception("destoryRefreshToken() 에러 - 조회 실패"));
}
@Override
public void sendAccessAndRefreshToken(HttpServletResponse response, String accessToken, String refreshToken) {
response.setStatus(HttpServletResponse.SC_OK);
setAccessTokenHeader(response, accessToken);
setRefreshTokenHeader(response, refreshToken);
Map<String, String> tokenMap = new HashMap<>();
tokenMap.put(ACCESS_TOKEN_SUBJECT, accessToken);
tokenMap.put(REFRESH_TOKEN_SUBJECT, refreshToken);
}
@Override
public void sendAccessToken(HttpServletResponse response, String accessToken) {
response.setStatus(HttpServletResponse.SC_OK);
setAccessTokenHeader(response, accessToken);
Map<String, String> tokenMap = new HashMap<>();
tokenMap.put(ACCESS_TOKEN_SUBJECT, accessToken);
}
@Override
public Optional<String> extractAccessToken(HttpServletRequest request) {
return Optional.ofNullable(request.getHeader(accessHeader)).filter(
accessToken -> accessToken.startsWith(BEARER)).map(accessToken -> accessToken.replace(BEARER, ""));
}
@Override
public Optional<String> extractRefreshToken(HttpServletRequest request) {
return Optional.ofNullable(request.getHeader(refreshHeader)).filter(
refreshToken -> refreshToken.startsWith(BEARER)).map(refreshToken -> refreshToken.replace(BEARER, ""));
}
@Override
public Optional<String> extractEmail(String accessToken) {
try {
return Optional.ofNullable(JWT.require(Algorithm.HMAC512(secret)).build().verify(accessToken)
.getClaim(USERNAME_CLAIM).asString());
} catch (Exception e) {
log.error(e.getMessage());
return Optional.empty();
}
}
@Override
public void setAccessTokenHeader(HttpServletResponse response, String accessToken) {
response.setHeader(accessHeader, accessToken);
}
@Override
public void setRefreshTokenHeader(HttpServletResponse response, String refreshToken) {
response.setHeader(refreshHeader, refreshToken);
}
@Override
public boolean isTokenValue(String token) {
try {
JWT.require(Algorithm.HMAC512(secret)).build().verify(token);
return true;
} catch (Exception e) {
log.error(e.getMessage());
return false;
}
}
- createAccessToken(String userEmail):
- 사용자의 이메일을 포함한 액세스 토큰 생성
- JWT.create()를 사용하여 JWT를 생성하고, withSubject()로 토큰의 주제를 설정하며, withExpiresAt()으로 만료 시간을 설정한다.
- withClaim(USERNAME_CLAIM, userEmail)으로 사용자 이메일을 JWT의 클레임에 추가한다. (토큰에 포함된 데이터의 조각으로, 토큰의 본문(payload) 부분에 저장)
- sign(Algorithm.HMAC512(secret))는 secret을 사용하여 JWT에 서명을 추가한다.
- createRefreshToken():
- 리프레시 토큰을 생성합니다. 리프레시 토큰은 이메일 등의 사용자 정보 없이 만료 시간만 설정된다.
- updateRefreshToken(String userEmail, String refreshToken):
- userRepository에서 사용자를 조회한 후, 해당 사용자의 리프레시 토큰을 갱신하고, 사용자가 없을 경우 예외를 던진다.
- destoryRefreshToken(String userEmail):
- 사용자의 리프레시 토큰을 파기한다. 사용자 조회 후, destroyRefreshToken() 메서드를 호출하여 리프레시 토큰을 삭제한다.
- sendAccessAndRefreshToken(HttpServletResponse response, String accessToken, String refreshToken):
- 응답에 액세스 토큰과 리프레시 토큰을 설정한다. setAccessTokenHeader()와 setRefreshTokenHeader()를 사용하여 헤더에 추가한다.
- sendAccessToken(HttpServletResponse response, String accessToken):
- 응답에 액세스 토큰만을 설정한다.
- extractAccessToken(HttpServletRequest request):
- 요청에서 액세스 토큰을 추출한다. Authorization 헤더에서 Bearer로 시작하는 토큰을 찾는다.
- extractRefreshToken(HttpServletRequest request):
- 요청에서 리프레시 토큰을 추출한다. Authorization-refresh 헤더에서 Bearer로 시작하는 토큰을 찾는다.
- extractEmail(String accessToken):
- 액세스 토큰에서 이메일을 추출한다. JWT를 검증하고, 이메일 클레임을 반환한다.
- setAccessTokenHeader(HttpServletResponse response, String accessToken):
- 응답의 Authorization 헤더에 액세스 토큰을 설정한다.
- setRefreshTokenHeader(HttpServletResponse response, String refreshToken):
- 응답의 Authorization-refresh 헤더에 리프레시 토큰을 설정한다.
- isTokenValue(String token):
- 주어진 토큰이 유효한지 검증한다. JWT를 검증하여 예외가 발생하지 않으면 유효한 토큰으로 간주한다.
728x90
반응형
'Framework > Spring' 카테고리의 다른 글
[Spring / ToyProject] Spring Security 설정 - 3_3 (0) | 2025.01.17 |
---|---|
[Spring / ToyProject] Spring Security 설정 - 3_2 (0) | 2025.01.16 |
[Spring / ToyProject] Spring Security 설정 - 2_2 (0) | 2025.01.16 |
[Spring / ToyProject] Spring Security 설정 - 2_1 (0) | 2025.01.14 |
[Spring / ToyProject] 에러일지: Request failed with status code 403 (0) | 2025.01.08 |