diff --git a/build.gradle b/build.gradle index 1e455837..711d84e5 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,11 @@ repositories { } dependencies { + //JWT + implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2' + implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2' + implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2' + // S3 AWS implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.6.RELEASE' diff --git a/src/main/java/com/moddy/server/common/exception/enums/ErrorCode.java b/src/main/java/com/moddy/server/common/exception/enums/ErrorCode.java index e532655e..86974f8f 100644 --- a/src/main/java/com/moddy/server/common/exception/enums/ErrorCode.java +++ b/src/main/java/com/moddy/server/common/exception/enums/ErrorCode.java @@ -7,6 +7,10 @@ @Getter @AllArgsConstructor public enum ErrorCode { + // 401 + TOKEN_TIME_EXPIRED_EXCEPTION(HttpStatus.UNAUTHORIZED, "토큰이 만료되었습니다. 다시 로그인 해주세요."), + + // 500 /** * 405 METHOD_NOT_ALLOWED */ diff --git a/src/main/java/com/moddy/server/config/jwt/JwtService.java b/src/main/java/com/moddy/server/config/jwt/JwtService.java new file mode 100644 index 00000000..f1fe4618 --- /dev/null +++ b/src/main/java/com/moddy/server/config/jwt/JwtService.java @@ -0,0 +1,104 @@ +package com.moddy.server.config.jwt; + +import com.moddy.server.common.exception.model.UnAuthorizedException; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.util.Base64; +import java.util.Date; + +import static com.moddy.server.common.exception.enums.ErrorCode.TOKEN_TIME_EXPIRED_EXCEPTION; + +@Service +public class JwtService { + @Value("${jwt.secret}") + private String jwtSecret; + private static final String USER_ID = "USER_ID"; + private static final String ACCESS_TOKEN = "ACCESS_TOKEN"; + private static final String REFRESH_TOKEN = "REFRESH_TOKEN"; + public static final long DAYS_IN_MILLISECONDS = 24 * 60 * 60 * 1000L; + public static final int ACCESS_TOKEN_EXPIRATION_DAYS = 30; + public static final int REFRESH_TOKEN_EXPIRATION_DAYS = 60; + + @PostConstruct + protected void init() { + jwtSecret = Base64.getEncoder() + .encodeToString(jwtSecret.getBytes(StandardCharsets.UTF_8)); + } + + public String createAccessToken(final String userId) { + final Claims claims = getAccessTokenClaims(); + + claims.put(USER_ID, userId); + return createToken(claims); + } + + public String createRefreshToken(final String userId) { + final Claims claims = getRefreshTokenClaims(); + + claims.put(USER_ID, userId); + return createToken(claims); + } + + public boolean verifyToken(final String token) { + try { + final Claims claims = getBody(token); + return true; + } catch (RuntimeException e) { + if (e instanceof ExpiredJwtException) { + throw new UnAuthorizedException(TOKEN_TIME_EXPIRED_EXCEPTION); + } + return false; + } + } + + public String getUserIdInToken(final String token) { + final Claims claims = getBody(token); + return (String) claims.get(USER_ID); + } + + private String createToken(final Claims claims) { + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setClaims(claims) + .signWith(getSigningKey()) + .compact(); + } + + private Claims getRefreshTokenClaims() { + final Date now = new Date(); + return Jwts.claims() + .setSubject(REFRESH_TOKEN) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + REFRESH_TOKEN_EXPIRATION_DAYS * DAYS_IN_MILLISECONDS)); + } + + private Claims getAccessTokenClaims() { + final Date now = new Date(); + return Jwts.claims() + .setSubject(ACCESS_TOKEN) + .setIssuedAt(now) + .setExpiration(new Date(now.getTime() + ACCESS_TOKEN_EXPIRATION_DAYS * DAYS_IN_MILLISECONDS)); + } + + private Claims getBody(final String token) { + return Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody(); + } + + private Key getSigningKey() { + final byte[] keyBytes = jwtSecret.getBytes(StandardCharsets.UTF_8); + return Keys.hmacShaKeyFor(keyBytes); + } +}