Skip to content

Commit

Permalink
feat: JWT generator
Browse files Browse the repository at this point in the history
  • Loading branch information
devmizz committed May 31, 2024
1 parent e1e2fc8 commit 340a5c2
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 0 deletions.
3 changes: 3 additions & 0 deletions app/api/src/main/java/org/example/config/ApiConfig.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.example.config;

import org.example.property.TokenProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@Import(UserApiConfig.class)
@EnableConfigurationProperties(TokenProperty.class)
@ComponentScan(basePackages = "org.example")
public class ApiConfig {

Expand Down
12 changes: 12 additions & 0 deletions app/api/src/main/java/org/example/property/TokenProperty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.example.property;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "token")
public record TokenProperty(
String secretKey,
Long accessTokenExpirationSeconds,
Long refreshTokenExpirationSeconds
) {

}
40 changes: 40 additions & 0 deletions app/api/src/main/java/org/example/security/JWTGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.example.security;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
import lombok.RequiredArgsConstructor;
import org.example.property.TokenProperty;
import org.example.security.dto.TokenParam;
import org.example.security.dto.UserParam;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class JWTGenerator {

private final TokenProperty tokenProperty;

public TokenParam generate(UserParam userParam, Date from) {
return TokenParam.builder()
.accessToken(createAccessToken(userParam, from))
.refreshToken(createRefreshToken(userParam, from))
.build();
}

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

private String createRefreshToken(UserParam userParam, Date from) {
return JWT.create().withSubject("RefreshToken")
.withClaim("claim", userParam.getTokenClaim())
.withExpiresAt(
new Date(from.getTime() + tokenProperty.refreshTokenExpirationSeconds())
).sign(Algorithm.HMAC512(tokenProperty.secretKey()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.example.security.dto;

import java.util.UUID;
import org.example.vo.UserRoleApiType;

public record AuthenticatedUser(
UUID userId,
UserRoleApiType role
) {

}
11 changes: 11 additions & 0 deletions app/api/src/main/java/org/example/security/dto/TokenParam.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.example.security.dto;

import lombok.Builder;

@Builder
public record TokenParam(
String accessToken,
String refreshToken
) {

}
18 changes: 18 additions & 0 deletions app/api/src/main/java/org/example/security/dto/UserParam.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.example.security.dto;

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

public record UserParam(
UUID userId,
UserRoleApiType role
) {

public Map<String, String> getTokenClaim() {
return Map.of(
"userId", userId.toString(),
"role", role.name()
);
}
}
5 changes: 5 additions & 0 deletions app/api/src/main/java/org/example/vo/UserRoleApiType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.example.vo;

public enum UserRoleApiType {
GUEST, USER, ADMIN
}
88 changes: 88 additions & 0 deletions app/api/src/test/java/org/example/security/JWTGeneratorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.example.security;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
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.vo.UserRoleApiType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class JWTGeneratorTest {

long hour = 3600000L;
long twoWeeks = 1209600000L;

TokenProperty tokenProperty = new TokenProperty(
"wehfiuhewiuhfhweiuhfiuwehifueiuwhfiuw",
hour,
twoWeeks
);

JWTGenerator tokenGenerator = new JWTGenerator(tokenProperty);
UserParam userParam = new UserParam(
UUID.randomUUID(),
UserRoleApiType.USER
);

@Test
@DisplayName("1시간 전에 생성된 AccessToken은 유효하지 않다.")
void accessTokenInvalidBeforeHourAgo() {
Date beforeHour = new Date(new Date().getTime() - hour);
TokenParam token = tokenGenerator.generate(userParam, beforeHour);

Assertions.assertThrowsExactly(
TokenExpiredException.class,
() -> JWT.require(Algorithm.HMAC512(tokenProperty.secretKey()))
.build()
.verify(token.accessToken())
);
}

@Test
@DisplayName("1시간 내에 생성된 AccessToken은 유효하다.")
void accessTokenValidAfterHourAgo() {
long second = 1000L;
Date beforeHourPlusSecond = new Date(new Date().getTime() - hour + second);
TokenParam token = tokenGenerator.generate(userParam, beforeHourPlusSecond);

Assertions.assertDoesNotThrow(
() -> JWT.require(Algorithm.HMAC512(tokenProperty.secretKey()))
.build()
.verify(token.accessToken())
);
}

@Test
@DisplayName("2주 전에 생성된 RefreshToken은 유효하지 않다.")
void refreshTokenInvalidBeforeHourAgo() {
Date beforeTwoWeeks = new Date(new Date().getTime() - twoWeeks);
TokenParam token = tokenGenerator.generate(userParam, beforeTwoWeeks);

Assertions.assertThrowsExactly(
TokenExpiredException.class,
() -> JWT.require(Algorithm.HMAC512(tokenProperty.secretKey()))
.build()
.verify(token.refreshToken())
);
}

@Test
@DisplayName("2주 내에 생성된 RefreshToken은 유효하다.")
void refreshTokenValidAfterHourAgo() {
long second = 1000L;
Date beforeTwoWeeksPlusSecond = new Date(new Date().getTime() - twoWeeks + second);
TokenParam token = tokenGenerator.generate(userParam, beforeTwoWeeksPlusSecond);

Assertions.assertDoesNotThrow(
() -> JWT.require(Algorithm.HMAC512(tokenProperty.secretKey()))
.build()
.verify(token.refreshToken())
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.example.vo;

public enum UserRole {
GUEST, USER, ADMIN
}
5 changes: 5 additions & 0 deletions app/src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ spring:
user:
name: user
password: password

token:
secret-key: ahRhwlglftmrkwkfehlaussksmsdjraksrmadmfqjfrjtdlrhdkwnwlflsmswlqdptjgodqhrgkrptkftndlTDmfrjtdlek
access-token-expiration-seconds: 3600000 # 1hour = 1000(=1s) * 60 * 60
refresh-token-expiration-seconds: 1209600000 # 2weeks = 1000(=1s) * 60 * 60 * 24 * 14

0 comments on commit 340a5c2

Please sign in to comment.