admin: RT 를 통한 AT 재 발급 시 IP&RT expired date time 체크 로직 추가

This commit is contained in:
geonhos 2024-05-23 15:11:15 +09:00
parent 6a1b4fe0c5
commit a3404947a0
12 changed files with 114 additions and 34 deletions

View File

@ -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(

View File

@ -0,0 +1,7 @@
package com.bpgroup.poc.admin.domain;
public class DomainException extends RuntimeException {
public DomainException(String message) {
super(message);
}
}

View File

@ -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);
}
}

View File

@ -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<AdminToken, Long> {
List<AdminToken> findByAdminIdAndState(Long adminId, TokenState state);
Optional<AdminToken> findLastByAdminIdAndIpAndState(Long adminId, String ip, TokenState state);
}

View File

@ -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);
}
}

View File

@ -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<AdminToken> 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();
}
}

View File

@ -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 {

View File

@ -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);

View File

@ -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))

View File

@ -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) {

View File

@ -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() {

View File

@ -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() {