Skip to content

Commit

Permalink
feat: JWT 의존성 변경
Browse files Browse the repository at this point in the history
  • Loading branch information
devmizz committed May 31, 2024
1 parent 96a7da7 commit 3a8345b
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 54 deletions.
4 changes: 3 additions & 1 deletion app/api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'

// jwt
implementation("com.auth0:java-jwt:4.3.0")
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'
}
8 changes: 8 additions & 0 deletions app/api/src/main/java/org/example/property/TokenProperty.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.example.property;

import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "token")
Expand All @@ -9,4 +12,9 @@ public record TokenProperty(
Long refreshTokenExpirationSeconds
) {

public SecretKey getBASE64URLSecretKey() {
return Keys.hmacShaKeyFor(
Decoders.BASE64URL.decode(secretKey)
);
}
}
11 changes: 11 additions & 0 deletions app/api/src/main/java/org/example/security/dto/UserParam.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import java.util.Map;
import java.util.UUID;
import lombok.Builder;
import org.example.vo.UserRoleApiType;

@Builder
public record UserParam(
UUID userId,
UserRoleApiType role
Expand All @@ -15,4 +17,13 @@ public Map<String, String> getTokenClaim() {
"role", role.name()
);
}

public static UserParam fromPayload(Object payload) {
Map<String, String> claim = (Map<String, String>) payload;

return UserParam.builder()
.userId(UUID.fromString(claim.get("userId")))
.role(UserRoleApiType.valueOf(claim.get("role")))
.build();
}
}
23 changes: 6 additions & 17 deletions app/api/src/main/java/org/example/security/token/JWTFilter.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
package org.example.security.token;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.databind.ObjectMapper;
import exception.BusinessException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.example.security.dto.AuthenticatedUser;
import org.example.security.dto.TokenParam;
import org.example.security.vo.TokenError;
import org.example.vo.UserRoleApiType;
import org.example.security.dto.UserParam;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
Expand Down Expand Up @@ -50,20 +45,14 @@ protected void doFilterInternal(

private void handleAccessToken(HttpServletRequest request) {
String accessToken = jwtProcessor.extractAccessToken(request);
DecodedJWT decodedJWT = jwtProcessor.decodeToken(accessToken);
saveOnSecurityContextHolder(decodedJWT);
UserParam userParam = jwtProcessor.extractUserFrom(accessToken);
saveOnSecurityContextHolder(userParam);
}

private void saveOnSecurityContextHolder(DecodedJWT decodedJWT) {
Map<String, Object> claims = decodedJWT.getClaim("claim").asMap();

if (!claims.containsKey("userId") || !claims.containsKey("role")) {
throw new BusinessException(TokenError.INVALID_CLAIM);
}

private void saveOnSecurityContextHolder(UserParam userParam) {
AuthenticatedUser authenticatedUser = AuthenticatedUser.builder()
.userId(UUID.fromString(claims.get("userId").toString()))
.role(UserRoleApiType.valueOf(claims.get("role").toString()))
.userId(userParam.userId())
.role(userParam.role())
.build();

SecurityContextHolder.getContext().setAuthentication(
Expand Down
25 changes: 15 additions & 10 deletions app/api/src/main/java/org/example/security/token/JWTGenerator.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.example.security.token;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import io.jsonwebtoken.Jwts;
import java.util.Date;
import lombok.RequiredArgsConstructor;
import org.example.property.TokenProperty;
Expand All @@ -23,18 +22,24 @@ public TokenParam generate(UserParam userParam, Date from) {
}

private String createAccessToken(UserParam userParam, Date from) {
return JWT.create().withSubject("AccessToken")
.withClaim("claim", userParam.getTokenClaim())
.withExpiresAt(
return Jwts.builder()
.subject("AccessToken")
.claims(userParam.getTokenClaim())
.expiration(
new Date(from.getTime() + tokenProperty.accessTokenExpirationSeconds())
).sign(Algorithm.HMAC512(tokenProperty.secretKey()));
)
.signWith(tokenProperty.getBASE64URLSecretKey())
.compact();
}

private String createRefreshToken(UserParam userParam, Date from) {
return JWT.create().withSubject("RefreshToken")
.withClaim("claim", userParam.getTokenClaim())
.withExpiresAt(
return Jwts.builder()
.subject("RefreshToken")
.claims(userParam.getTokenClaim())
.expiration(
new Date(from.getTime() + tokenProperty.refreshTokenExpirationSeconds())
).sign(Algorithm.HMAC512(tokenProperty.secretKey()));
)
.signWith(tokenProperty.getBASE64URLSecretKey())
.compact();
}
}
48 changes: 39 additions & 9 deletions app/api/src/main/java/org/example/security/token/JWTProcessor.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package org.example.security.token;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import exception.BusinessException;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import jakarta.servlet.http.HttpServletRequest;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.example.property.TokenProperty;
import org.example.security.dto.UserParam;
import org.example.security.vo.TokenError;
import org.springframework.stereotype.Component;

Expand Down Expand Up @@ -37,15 +38,44 @@ public String extractRefreshToken(HttpServletRequest request) {
return refresh;
}

public DecodedJWT decodeToken(String token) {
public UserParam extractUserFrom(String token) {
Object payload = parsePayload(token);
return convertPayloadToUserParam(payload);
}

public UUID getUserIdFromExpiredToken(String token) {
try {
parseToken(token);
} catch (ExpiredJwtException e) {
return UUID.fromString(e.getClaims().get("userId", String.class));
}

throw new BusinessException(TokenError.UNEXPIRED_TOKEN);
}

private Jwt<?, ?> parseToken(String token) {
return Jwts.parser()
.verifyWith(tokenProperty.getBASE64URLSecretKey())
.build()
.parse(token);
}

private Object parsePayload(String token) {
try {
return JWT.require(Algorithm.HMAC512(tokenProperty.secretKey()))
.build()
.verify(token);
} catch (TokenExpiredException e) {
return parseToken(token)
.getPayload();
} catch (ExpiredJwtException e) {
throw new BusinessException(TokenError.EXPIRED_TOKEN);
} catch (Exception e) {
throw new BusinessException(TokenError.INVALID_TOKEN);
}
}

private UserParam convertPayloadToUserParam(Object payload) {
try {
return UserParam.fromPayload(payload);
} catch (Exception e) {
throw new BusinessException(TokenError.INVALID_CLAIM);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.example.security.token;

import com.auth0.jwt.interfaces.DecodedJWT;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
Expand Down
22 changes: 22 additions & 0 deletions app/api/src/main/java/org/example/security/vo/TokenError.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,5 +90,27 @@ public String getClientMessage() {
public String getLogMessage() {
return "토큰 claim 구성에 오류가 있습니다.";
}
},

UNEXPIRED_TOKEN {
@Override
public int getHttpStatus() {
return 400;
}

@Override
public String getErrorCode() {
return "TKN-004";
}

@Override
public String getClientMessage() {
return "토큰의 상태와 일치하지 않는 요청입니다.";
}

@Override
public String getLogMessage() {
return "만료되지 않은 토큰에 대해, 만료 상황에 대한 로직이 시행되었습니다.";
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package org.example.security;
package org.example.security.token;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import java.util.Date;
import java.util.UUID;
import org.example.property.TokenProperty;
import org.example.security.dto.TokenParam;
import org.example.security.dto.UserParam;
import org.example.security.token.JWTGenerator;
import org.example.vo.UserRoleApiType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
Expand All @@ -20,7 +18,7 @@ class JWTGeneratorTest {
long twoWeeks = 1209600000L;

TokenProperty tokenProperty = new TokenProperty(
"wehfiuhewiuhfhweiuhfiuwehifueiuwhfiuw",
"wehfiuhewiuhfhweiuhfiuwehifueisdfsdfsdfdsfsduwhfiuw",
hour,
twoWeeks
);
Expand All @@ -38,10 +36,11 @@ void accessTokenInvalidBeforeHourAgo() {
TokenParam token = tokenGenerator.generate(userParam, beforeHour);

Assertions.assertThrowsExactly(
TokenExpiredException.class,
() -> JWT.require(Algorithm.HMAC512(tokenProperty.secretKey()))
ExpiredJwtException.class,
() -> Jwts.parser()
.verifyWith(tokenProperty.getBASE64URLSecretKey())
.build()
.verify(token.accessToken())
.parse(token.accessToken())
);
}

Expand All @@ -53,9 +52,10 @@ void accessTokenValidAfterHourAgo() {
TokenParam token = tokenGenerator.generate(userParam, beforeHourPlusSecond);

Assertions.assertDoesNotThrow(
() -> JWT.require(Algorithm.HMAC512(tokenProperty.secretKey()))
() -> Jwts.parser()
.verifyWith(tokenProperty.getBASE64URLSecretKey())
.build()
.verify(token.accessToken())
.parse(token.accessToken())
);
}

Expand All @@ -66,10 +66,11 @@ void refreshTokenInvalidBeforeHourAgo() {
TokenParam token = tokenGenerator.generate(userParam, beforeTwoWeeks);

Assertions.assertThrowsExactly(
TokenExpiredException.class,
() -> JWT.require(Algorithm.HMAC512(tokenProperty.secretKey()))
ExpiredJwtException.class,
() -> Jwts.parser()
.verifyWith(tokenProperty.getBASE64URLSecretKey())
.build()
.verify(token.refreshToken())
.parse(token.refreshToken())
);
}

Expand All @@ -81,9 +82,10 @@ void refreshTokenValidAfterHourAgo() {
TokenParam token = tokenGenerator.generate(userParam, beforeTwoWeeksPlusSecond);

Assertions.assertDoesNotThrow(
() -> JWT.require(Algorithm.HMAC512(tokenProperty.secretKey()))
() -> Jwts.parser()
.verifyWith(tokenProperty.getBASE64URLSecretKey())
.build()
.verify(token.refreshToken())
.parse(token.refreshToken())
);
}
}
Loading

0 comments on commit 3a8345b

Please sign in to comment.