diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/app/jwt/JwtTokenAppService.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/app/jwt/JwtTokenAppService.java index 23b4029..aa6e8ca 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/app/jwt/JwtTokenAppService.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/app/jwt/JwtTokenAppService.java @@ -3,11 +3,14 @@ package com.bpgroup.poc.admin.app.jwt; import com.bpgroup.poc.admin.domain.admin.entity.Admin; import com.bpgroup.poc.admin.domain.admin.service.AdminService; import com.bpgroup.poc.admin.domain.admin.service.AdminTokenCreateCommand; +import com.bpgroup.poc.admin.domain.admin.service.AdminTokenExpireCommand; import com.bpgroup.poc.admin.domain.admin.service.AdminTokenService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; + @Service @RequiredArgsConstructor @Transactional @@ -16,9 +19,30 @@ public class JwtTokenAppService { private final AdminService adminService; private final AdminTokenService adminTokenService; - public void save(JwtTokenRequest request) { + public void saveNewToken(JwtTokenRequest request) { Admin findAdmin = adminService.find(request.getLoginId()).orElseThrow(() -> new IllegalArgumentException("Not found admin")); - adminTokenService.expire(findAdmin.getId()); + + adminTokenService.create( + AdminTokenCreateCommand.of( + request.getIp(), + request.getAccessToken(), + request.getRefreshToken(), + request.getExpiredRefreshToken(), + request.getState(), + findAdmin + ) + ); + } + + public void saveRegenerateToken(JwtTokenRequest request) { + Admin findAdmin = adminService.find(request.getLoginId()).orElseThrow(() -> new IllegalArgumentException("Not found admin")); + adminTokenService.expire( + AdminTokenExpireCommand.of( + findAdmin.getId(), + request.getIp(), + LocalDateTime.now() + ) + ); adminTokenService.create( AdminTokenCreateCommand.of( diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/DomainException.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/DomainException.java new file mode 100644 index 0000000..0a71c29 --- /dev/null +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/DomainException.java @@ -0,0 +1,7 @@ +package com.bpgroup.poc.admin.domain; + +public class DomainException extends RuntimeException { + public DomainException(String message) { + super(message); + } +} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminToken.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminToken.java index ec3341b..8ddfdac 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminToken.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminToken.java @@ -3,10 +3,12 @@ package com.bpgroup.poc.admin.domain.admin.entity; import com.bpgroup.poc.admin.domain.BaseEntity; import jakarta.persistence.*; import lombok.AccessLevel; +import lombok.Getter; import lombok.NoArgsConstructor; import java.time.LocalDateTime; +@Getter @Entity @Table(name = "admin_token") @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -56,7 +58,11 @@ public class AdminToken extends BaseEntity { return new AdminToken(ip, accessToken, refreshToken, expiredRefreshTokenDateTime, state, admin); } - public void expire(TokenState state) { - this.state = state; + public void expire() { + this.state = TokenState.EXPIRED; + } + + public boolean isNotExpired(LocalDateTime dateTime) { + return this.expiredRefreshTokenDateTime.isAfter(dateTime); } } diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminTokenRepository.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminTokenRepository.java index 36235e5..bad968a 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminTokenRepository.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/entity/AdminTokenRepository.java @@ -2,8 +2,8 @@ package com.bpgroup.poc.admin.domain.admin.entity; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.List; +import java.util.Optional; public interface AdminTokenRepository extends JpaRepository { - List findByAdminIdAndState(Long adminId, TokenState state); + Optional findLastByAdminIdAndIpAndState(Long adminId, String ip, TokenState state); } diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminTokenExpireCommand.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminTokenExpireCommand.java new file mode 100644 index 0000000..7f592a2 --- /dev/null +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminTokenExpireCommand.java @@ -0,0 +1,28 @@ +package com.bpgroup.poc.admin.domain.admin.service; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class AdminTokenExpireCommand { + @NotNull + private final Long adminId; + @NotBlank + private final String ip; + @NotNull + private final LocalDateTime dateTime; + + public static AdminTokenExpireCommand of( + Long adminId, + String ip, + LocalDateTime dateTime + ) { + return new AdminTokenExpireCommand(adminId, ip, dateTime); + } +} diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminTokenService.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminTokenService.java index 64612d3..9a779aa 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminTokenService.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/domain/admin/service/AdminTokenService.java @@ -1,6 +1,8 @@ package com.bpgroup.poc.admin.domain.admin.service; -import com.bpgroup.poc.admin.domain.admin.entity.*; +import com.bpgroup.poc.admin.domain.admin.entity.AdminToken; +import com.bpgroup.poc.admin.domain.admin.entity.AdminTokenRepository; +import com.bpgroup.poc.admin.domain.admin.entity.TokenState; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; @@ -8,7 +10,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import java.util.List; +import java.time.LocalDateTime; @Service @RequiredArgsConstructor @@ -22,11 +24,15 @@ public class AdminTokenService { adminTokenRepository.save(command.toEntity()); } - public void expire(@NotNull Long adminId) { - List adminTokens = adminTokenRepository.findByAdminIdAndState(adminId, TokenState.NORMAL); - adminTokens.forEach( - adminToken -> adminToken.expire(TokenState.EXPIRED) - ); + public void expire(@NotNull @Valid AdminTokenExpireCommand command) { + AdminToken findAdminToken = adminTokenRepository.findLastByAdminIdAndIpAndState(command.getAdminId(), command.getIp(), TokenState.NORMAL) + .orElseThrow(() -> new IllegalArgumentException("Not found token")); + + if (findAdminToken.isNotExpired(LocalDateTime.now())) { + throw new IllegalArgumentException("Token is expired"); + } + + findAdminToken.expire(); } } diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationProvider.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationProvider.java index 570a382..cd4cd90 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationProvider.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationProvider.java @@ -1,20 +1,18 @@ package com.bpgroup.poc.admin.security.authentication; -import com.bpgroup.poc.admin.app.authentication.AuthenticationResult; import com.bpgroup.poc.admin.app.authentication.AuthenticationAppService; +import com.bpgroup.poc.admin.app.authentication.AuthenticationResult; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; -import org.springframework.transaction.annotation.Transactional; @RequiredArgsConstructor public class CustomAuthenticationProvider implements AuthenticationProvider { private final AuthenticationAppService authenticationAppService; - @Transactional @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { try { diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationSuccessHandler.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationSuccessHandler.java index 219a742..6fdbce4 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationSuccessHandler.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/authentication/CustomAuthenticationSuccessHandler.java @@ -22,7 +22,7 @@ public class CustomAuthenticationSuccessHandler implements AuthenticationSuccess public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { String username = (String) authentication.getPrincipal(); - jwtTokenProvider.generateToken(request, response, username); + jwtTokenProvider.generateToken(request, response, username, JwtTokenProvider.JwtTokenIssueType.GENERATE); response.setStatus(HttpServletResponse.SC_OK); response.setContentType(MediaType.APPLICATION_JSON_VALUE); diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenProvider.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenProvider.java index ff0be77..6852215 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenProvider.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenProvider.java @@ -1,8 +1,10 @@ package com.bpgroup.poc.admin.security.jwt; -import com.bpgroup.poc.admin.app.jwt.JwtTokenRequest; import com.bpgroup.poc.admin.app.jwt.JwtTokenAppService; +import com.bpgroup.poc.admin.app.jwt.JwtTokenRequest; import com.bpgroup.poc.admin.domain.admin.entity.TokenState; +import com.bpgroup.poc.admin.security.jwt.exception.JwtTokenExpiredException; +import com.bpgroup.poc.admin.security.jwt.exception.JwtTokenInvalidException; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; @@ -27,23 +29,30 @@ public class JwtTokenProvider { // TODO: 추후 Key 별도 관리 필요 private static final SecretKey JWT_KEY = Keys.hmacShaKeyFor(JwtTokenConstants.KEY.getBytes(StandardCharsets.UTF_8)); - public void generateToken(HttpServletRequest request, HttpServletResponse response, String username) { + public enum JwtTokenIssueType { + GENERATE, REGENERATE + } + + public void generateToken(HttpServletRequest request, HttpServletResponse response, String username, JwtTokenIssueType issueType) { Cookie accessTokenCookie = createCookieWithToken(username, JwtTokenProvider.JwtTokenType.ACCESS); response.addCookie(accessTokenCookie); Cookie refreshTokenCookie = createCookieWithToken(username, JwtTokenProvider.JwtTokenType.REFRESH); response.addCookie(refreshTokenCookie); - jwtTokenAppService.save( - JwtTokenRequest.of( - request.getRemoteAddr(), - accessTokenCookie.getValue(), - refreshTokenCookie.getValue(), - LocalDateTime.now().plusSeconds(refreshTokenCookie.getMaxAge()), - TokenState.NORMAL, - username - ) + JwtTokenRequest jwtTokenRequest = JwtTokenRequest.of( + request.getRemoteAddr(), + accessTokenCookie.getValue(), + refreshTokenCookie.getValue(), + LocalDateTime.now().plusSeconds(refreshTokenCookie.getMaxAge()), + TokenState.NORMAL, + username ); + + switch (issueType) { + case GENERATE -> jwtTokenAppService.saveNewToken(jwtTokenRequest); + case REGENERATE -> jwtTokenAppService.saveRegenerateToken(jwtTokenRequest); + } } public enum JwtTokenType { @@ -65,7 +74,7 @@ public class JwtTokenProvider { expirationTime = JwtTokenConstants.RT_EXPIRATION_TIME; } - String token = generateToken(JwtTokenConstants.ISSUER, subject, username, expirationTime); + String token = generateToken(subject, username, expirationTime); Cookie tokenCookie = new Cookie(tokenName, token); tokenCookie.setHttpOnly(true); @@ -74,9 +83,9 @@ public class JwtTokenProvider { return tokenCookie; } - private String generateToken(String issuer, String subject, String username, long expirationTime) { + private String generateToken(String subject, String username, long expirationTime) { return Jwts.builder() - .issuer(issuer) + .issuer(JwtTokenConstants.ISSUER) .subject(subject) .claim("username", username) .issuedAt(new Date()) .expiration(getExpirationDate(expirationTime)) diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenValidateFilter.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenValidateFilter.java index df789da..1a8787c 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenValidateFilter.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenValidateFilter.java @@ -1,5 +1,7 @@ package com.bpgroup.poc.admin.security.jwt; +import com.bpgroup.poc.admin.security.jwt.exception.JwtTokenExpiredException; +import com.bpgroup.poc.admin.security.jwt.exception.JwtTokenInvalidException; import io.jsonwebtoken.Claims; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -56,7 +58,7 @@ public class JwtTokenValidateFilter extends OncePerRequestFilter { Claims claims = jwtTokenProvider.getClaims(refreshToken); String username = claims.get("username", String.class); - jwtTokenProvider.generateToken(request, response, username); + jwtTokenProvider.generateToken(request, response, username, JwtTokenProvider.JwtTokenIssueType.REGENERATE); setSecurityContext(username); } catch (JwtTokenExpiredException e) { diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenExpiredException.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/exception/JwtTokenExpiredException.java similarity index 69% rename from poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenExpiredException.java rename to poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/exception/JwtTokenExpiredException.java index 3bbdc57..0447abc 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenExpiredException.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/exception/JwtTokenExpiredException.java @@ -1,4 +1,4 @@ -package com.bpgroup.poc.admin.security.jwt; +package com.bpgroup.poc.admin.security.jwt.exception; public class JwtTokenExpiredException extends Exception { public JwtTokenExpiredException() { diff --git a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenInvalidException.java b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/exception/JwtTokenInvalidException.java similarity index 69% rename from poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenInvalidException.java rename to poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/exception/JwtTokenInvalidException.java index 0f92e2f..d942e61 100644 --- a/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/JwtTokenInvalidException.java +++ b/poc/admin/src/main/java/com/bpgroup/poc/admin/security/jwt/exception/JwtTokenInvalidException.java @@ -1,4 +1,4 @@ -package com.bpgroup.poc.admin.security.jwt; +package com.bpgroup.poc.admin.security.jwt.exception; public class JwtTokenInvalidException extends Exception { public JwtTokenInvalidException() {