admin: RT 를 통한 AT 재 발급 시 IP&RT expired date time 체크 로직 추가
This commit is contained in:
parent
6a1b4fe0c5
commit
a3404947a0
|
|
@ -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.entity.Admin;
|
||||||
import com.bpgroup.poc.admin.domain.admin.service.AdminService;
|
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.AdminTokenCreateCommand;
|
||||||
|
import com.bpgroup.poc.admin.domain.admin.service.AdminTokenExpireCommand;
|
||||||
import com.bpgroup.poc.admin.domain.admin.service.AdminTokenService;
|
import com.bpgroup.poc.admin.domain.admin.service.AdminTokenService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Transactional
|
@Transactional
|
||||||
|
|
@ -16,9 +19,30 @@ public class JwtTokenAppService {
|
||||||
private final AdminService adminService;
|
private final AdminService adminService;
|
||||||
private final AdminTokenService adminTokenService;
|
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"));
|
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(
|
adminTokenService.create(
|
||||||
AdminTokenCreateCommand.of(
|
AdminTokenCreateCommand.of(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.bpgroup.poc.admin.domain;
|
||||||
|
|
||||||
|
public class DomainException extends RuntimeException {
|
||||||
|
public DomainException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,10 +3,12 @@ package com.bpgroup.poc.admin.domain.admin.entity;
|
||||||
import com.bpgroup.poc.admin.domain.BaseEntity;
|
import com.bpgroup.poc.admin.domain.BaseEntity;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Getter
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "admin_token")
|
@Table(name = "admin_token")
|
||||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
|
@ -56,7 +58,11 @@ public class AdminToken extends BaseEntity {
|
||||||
return new AdminToken(ip, accessToken, refreshToken, expiredRefreshTokenDateTime, state, admin);
|
return new AdminToken(ip, accessToken, refreshToken, expiredRefreshTokenDateTime, state, admin);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void expire(TokenState state) {
|
public void expire() {
|
||||||
this.state = state;
|
this.state = TokenState.EXPIRED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNotExpired(LocalDateTime dateTime) {
|
||||||
|
return this.expiredRefreshTokenDateTime.isAfter(dateTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ package com.bpgroup.poc.admin.domain.admin.entity;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface AdminTokenRepository extends JpaRepository<AdminToken, Long> {
|
public interface AdminTokenRepository extends JpaRepository<AdminToken, Long> {
|
||||||
List<AdminToken> findByAdminIdAndState(Long adminId, TokenState state);
|
Optional<AdminToken> findLastByAdminIdAndIpAndState(Long adminId, String ip, TokenState state);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.bpgroup.poc.admin.domain.admin.service;
|
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.Valid;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
@ -8,7 +10,7 @@ import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
import java.util.List;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
|
@ -22,11 +24,15 @@ public class AdminTokenService {
|
||||||
adminTokenRepository.save(command.toEntity());
|
adminTokenRepository.save(command.toEntity());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void expire(@NotNull Long adminId) {
|
public void expire(@NotNull @Valid AdminTokenExpireCommand command) {
|
||||||
List<AdminToken> adminTokens = adminTokenRepository.findByAdminIdAndState(adminId, TokenState.NORMAL);
|
AdminToken findAdminToken = adminTokenRepository.findLastByAdminIdAndIpAndState(command.getAdminId(), command.getIp(), TokenState.NORMAL)
|
||||||
adminTokens.forEach(
|
.orElseThrow(() -> new IllegalArgumentException("Not found token"));
|
||||||
adminToken -> adminToken.expire(TokenState.EXPIRED)
|
|
||||||
);
|
if (findAdminToken.isNotExpired(LocalDateTime.now())) {
|
||||||
|
throw new IllegalArgumentException("Token is expired");
|
||||||
|
}
|
||||||
|
|
||||||
|
findAdminToken.expire();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,18 @@
|
||||||
package com.bpgroup.poc.admin.security.authentication;
|
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.AuthenticationAppService;
|
||||||
|
import com.bpgroup.poc.admin.app.authentication.AuthenticationResult;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.core.AuthenticationException;
|
import org.springframework.security.core.AuthenticationException;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class CustomAuthenticationProvider implements AuthenticationProvider {
|
public class CustomAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
|
||||||
private final AuthenticationAppService authenticationAppService;
|
private final AuthenticationAppService authenticationAppService;
|
||||||
|
|
||||||
@Transactional
|
|
||||||
@Override
|
@Override
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ public class CustomAuthenticationSuccessHandler implements AuthenticationSuccess
|
||||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
|
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
|
||||||
String username = (String) authentication.getPrincipal();
|
String username = (String) authentication.getPrincipal();
|
||||||
|
|
||||||
jwtTokenProvider.generateToken(request, response, username);
|
jwtTokenProvider.generateToken(request, response, username, JwtTokenProvider.JwtTokenIssueType.GENERATE);
|
||||||
|
|
||||||
response.setStatus(HttpServletResponse.SC_OK);
|
response.setStatus(HttpServletResponse.SC_OK);
|
||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.bpgroup.poc.admin.security.jwt;
|
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.JwtTokenAppService;
|
||||||
|
import com.bpgroup.poc.admin.app.jwt.JwtTokenRequest;
|
||||||
import com.bpgroup.poc.admin.domain.admin.entity.TokenState;
|
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.Claims;
|
||||||
import io.jsonwebtoken.ExpiredJwtException;
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
|
|
@ -27,23 +29,30 @@ public class JwtTokenProvider {
|
||||||
// TODO: 추후 Key 별도 관리 필요
|
// TODO: 추후 Key 별도 관리 필요
|
||||||
private static final SecretKey JWT_KEY = Keys.hmacShaKeyFor(JwtTokenConstants.KEY.getBytes(StandardCharsets.UTF_8));
|
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);
|
Cookie accessTokenCookie = createCookieWithToken(username, JwtTokenProvider.JwtTokenType.ACCESS);
|
||||||
response.addCookie(accessTokenCookie);
|
response.addCookie(accessTokenCookie);
|
||||||
|
|
||||||
Cookie refreshTokenCookie = createCookieWithToken(username, JwtTokenProvider.JwtTokenType.REFRESH);
|
Cookie refreshTokenCookie = createCookieWithToken(username, JwtTokenProvider.JwtTokenType.REFRESH);
|
||||||
response.addCookie(refreshTokenCookie);
|
response.addCookie(refreshTokenCookie);
|
||||||
|
|
||||||
jwtTokenAppService.save(
|
JwtTokenRequest jwtTokenRequest = JwtTokenRequest.of(
|
||||||
JwtTokenRequest.of(
|
request.getRemoteAddr(),
|
||||||
request.getRemoteAddr(),
|
accessTokenCookie.getValue(),
|
||||||
accessTokenCookie.getValue(),
|
refreshTokenCookie.getValue(),
|
||||||
refreshTokenCookie.getValue(),
|
LocalDateTime.now().plusSeconds(refreshTokenCookie.getMaxAge()),
|
||||||
LocalDateTime.now().plusSeconds(refreshTokenCookie.getMaxAge()),
|
TokenState.NORMAL,
|
||||||
TokenState.NORMAL,
|
username
|
||||||
username
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
switch (issueType) {
|
||||||
|
case GENERATE -> jwtTokenAppService.saveNewToken(jwtTokenRequest);
|
||||||
|
case REGENERATE -> jwtTokenAppService.saveRegenerateToken(jwtTokenRequest);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum JwtTokenType {
|
public enum JwtTokenType {
|
||||||
|
|
@ -65,7 +74,7 @@ public class JwtTokenProvider {
|
||||||
expirationTime = JwtTokenConstants.RT_EXPIRATION_TIME;
|
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);
|
Cookie tokenCookie = new Cookie(tokenName, token);
|
||||||
tokenCookie.setHttpOnly(true);
|
tokenCookie.setHttpOnly(true);
|
||||||
|
|
@ -74,9 +83,9 @@ public class JwtTokenProvider {
|
||||||
return tokenCookie;
|
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()
|
return Jwts.builder()
|
||||||
.issuer(issuer)
|
.issuer(JwtTokenConstants.ISSUER)
|
||||||
.subject(subject)
|
.subject(subject)
|
||||||
.claim("username", username) .issuedAt(new Date())
|
.claim("username", username) .issuedAt(new Date())
|
||||||
.expiration(getExpirationDate(expirationTime))
|
.expiration(getExpirationDate(expirationTime))
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.bpgroup.poc.admin.security.jwt;
|
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 io.jsonwebtoken.Claims;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
|
|
@ -56,7 +58,7 @@ public class JwtTokenValidateFilter extends OncePerRequestFilter {
|
||||||
Claims claims = jwtTokenProvider.getClaims(refreshToken);
|
Claims claims = jwtTokenProvider.getClaims(refreshToken);
|
||||||
String username = claims.get("username", String.class);
|
String username = claims.get("username", String.class);
|
||||||
|
|
||||||
jwtTokenProvider.generateToken(request, response, username);
|
jwtTokenProvider.generateToken(request, response, username, JwtTokenProvider.JwtTokenIssueType.REGENERATE);
|
||||||
|
|
||||||
setSecurityContext(username);
|
setSecurityContext(username);
|
||||||
} catch (JwtTokenExpiredException e) {
|
} catch (JwtTokenExpiredException e) {
|
||||||
|
|
|
||||||
|
|
@ -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 class JwtTokenExpiredException extends Exception {
|
||||||
public JwtTokenExpiredException() {
|
public JwtTokenExpiredException() {
|
||||||
|
|
@ -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 class JwtTokenInvalidException extends Exception {
|
||||||
public JwtTokenInvalidException() {
|
public JwtTokenInvalidException() {
|
||||||
Loading…
Reference in New Issue