admin: 토큰 저장 로직 추가

This commit is contained in:
geonhos 2024-05-22 15:33:22 +09:00
parent 62952885ff
commit 898cf744ab
11 changed files with 219 additions and 26 deletions

View File

@ -0,0 +1,30 @@
package com.bpgroup.poc.admin.app.jwt;
import com.bpgroup.poc.admin.domain.base.admin.entity.TokenState;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.time.LocalDateTime;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class JwtTokenRequest {
private final String ip;
private final String accessToken;
private final String refreshToken;
private final LocalDateTime expiredRefreshToken;
private final TokenState state;
private final String loginId;
public static JwtTokenRequest of(
String ip,
String accessToken,
String refreshToken,
LocalDateTime expiredRefreshToken,
TokenState state,
String loginId
) {
return new JwtTokenRequest(ip, accessToken, refreshToken, expiredRefreshToken, state, loginId);
}
}

View File

@ -0,0 +1,35 @@
package com.bpgroup.poc.admin.app.jwt;
import com.bpgroup.poc.admin.domain.base.admin.entity.Admin;
import com.bpgroup.poc.admin.domain.base.admin.service.AdminService;
import com.bpgroup.poc.admin.domain.base.admin.service.AdminTokenCreateCommand;
import com.bpgroup.poc.admin.domain.base.admin.service.AdminTokenService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional
public class JwtTokenService {
private final AdminService adminService;
private final AdminTokenService adminTokenService;
public void save(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
)
);
}
}

View File

@ -1,5 +1,6 @@
package com.bpgroup.poc.admin.domain.base.admin.entity; package com.bpgroup.poc.admin.domain.base.admin.entity;
import com.bpgroup.poc.admin.domain.base.BaseEntity;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -9,23 +10,53 @@ import java.time.LocalDateTime;
@Entity @Entity
@Table(name = "admin_token") @Table(name = "admin_token")
@NoArgsConstructor(access = AccessLevel.PROTECTED) @NoArgsConstructor(access = AccessLevel.PROTECTED)
public class AdminToken { public class AdminToken extends BaseEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@Column(name = "ip", nullable = false)
private String ip;
@Column(name = "access_token", nullable = false) @Column(name = "access_token", nullable = false)
private String accessToken; private String accessToken;
@Column(name = "refresh_token", length = 32, nullable = false) @Column(name = "refresh_token", nullable = false)
private String refreshToken; private String refreshToken;
@Column(name = "expired_refresh_token", nullable = false) @Column(name = "expired_refresh_token_date_time", nullable = false)
private LocalDateTime expiredRefreshToken; private LocalDateTime expiredRefreshTokenDateTime;
@Column(name = "state", nullable = false)
@Enumerated(EnumType.STRING)
private TokenState state;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "admin_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT), nullable = false) @JoinColumn(name = "admin_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT), nullable = false)
private Admin admin; private Admin admin;
private AdminToken(String ip, String accessToken, String refreshToken, LocalDateTime expiredRefreshTokenDateTime, TokenState state, Admin admin) {
this.ip = ip;
this.accessToken = accessToken;
this.refreshToken = refreshToken;
this.expiredRefreshTokenDateTime = expiredRefreshTokenDateTime;
this.state = state;
this.admin = admin;
}
public static AdminToken createOf(
String ip,
String accessToken,
String refreshToken,
LocalDateTime expiredRefreshTokenDateTime,
TokenState state,
Admin admin
) {
return new AdminToken(ip, accessToken, refreshToken, expiredRefreshTokenDateTime, state, admin);
}
public void expire(TokenState state) {
this.state = state;
}
} }

View File

@ -2,5 +2,8 @@ package com.bpgroup.poc.admin.domain.base.admin.entity;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface AdminTokenRepository extends JpaRepository<AdminToken, Long> { public interface AdminTokenRepository extends JpaRepository<AdminToken, Long> {
List<AdminToken> findByAdminIdAndState(Long adminId, TokenState state);
} }

View File

@ -0,0 +1,5 @@
package com.bpgroup.poc.admin.domain.base.admin.entity;
public enum TokenState {
NORMAL, EXPIRED
}

View File

@ -0,0 +1,45 @@
package com.bpgroup.poc.admin.domain.base.admin.service;
import com.bpgroup.poc.admin.domain.base.admin.entity.Admin;
import com.bpgroup.poc.admin.domain.base.admin.entity.AdminToken;
import com.bpgroup.poc.admin.domain.base.admin.entity.TokenState;
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 AdminTokenCreateCommand {
@NotBlank
private final String ip;
@NotBlank
private final String accessToken;
@NotBlank
private final String refreshToken;
@NotNull
private final LocalDateTime expiredRefreshTokenDateTime;
@NotNull
private final TokenState state;
@NotNull
private final Admin admin;
public static AdminTokenCreateCommand of(
String ip,
String accessToken,
String refreshToken,
LocalDateTime expiredRefreshTokenDateTime,
TokenState state,
Admin admin
) {
return new AdminTokenCreateCommand(ip, accessToken, refreshToken, expiredRefreshTokenDateTime, state, admin);
}
public AdminToken toEntity() {
return AdminToken.createOf(ip, accessToken, refreshToken, expiredRefreshTokenDateTime, state, admin);
}
}

View File

@ -0,0 +1,32 @@
package com.bpgroup.poc.admin.domain.base.admin.service;
import com.bpgroup.poc.admin.domain.base.admin.entity.*;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.List;
@Service
@RequiredArgsConstructor
@Validated
@Transactional
public class AdminTokenService {
private final AdminTokenRepository adminTokenRepository;
public void create(@NotNull @Valid AdminTokenCreateCommand command) {
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)
);
}
}

View File

@ -1,5 +1,6 @@
package com.bpgroup.poc.admin.security; package com.bpgroup.poc.admin.security;
import com.bpgroup.poc.admin.app.jwt.JwtTokenService;
import com.bpgroup.poc.admin.security.authentication.*; import com.bpgroup.poc.admin.security.authentication.*;
import com.bpgroup.poc.admin.app.authentication.AuthenticationService; import com.bpgroup.poc.admin.app.authentication.AuthenticationService;
import com.bpgroup.poc.admin.security.authorization.CustomAccessDeniedHandler; import com.bpgroup.poc.admin.security.authorization.CustomAccessDeniedHandler;
@ -37,6 +38,7 @@ public class SecurityConfig {
private final AuthenticationService authenticationService; private final AuthenticationService authenticationService;
private final AuthorizationService authorizationService; private final AuthorizationService authorizationService;
private final JwtTokenService jwtTokenService;
private final JwtTokenProvider jwtTokenProvider; private final JwtTokenProvider jwtTokenProvider;

View File

@ -3,7 +3,6 @@ package com.bpgroup.poc.admin.security.authentication;
import com.bpgroup.poc.admin.app.authentication.AuthenticationResponse; import com.bpgroup.poc.admin.app.authentication.AuthenticationResponse;
import com.bpgroup.poc.admin.security.jwt.JwtTokenProvider; import com.bpgroup.poc.admin.security.jwt.JwtTokenProvider;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -23,11 +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();
Cookie accessTokenCookie = jwtTokenProvider.createCookieWithToken(username, JwtTokenProvider.JwtTokenType.ACCESS); jwtTokenProvider.generateToken(request, response, username);
response.addCookie(accessTokenCookie);
Cookie refreshTokenCookie = jwtTokenProvider.createCookieWithToken(username, JwtTokenProvider.JwtTokenType.REFRESH);
response.addCookie(refreshTokenCookie);
response.setStatus(HttpServletResponse.SC_OK); response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setContentType(MediaType.APPLICATION_JSON_VALUE);

View File

@ -1,27 +1,56 @@
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.JwtTokenService;
import com.bpgroup.poc.admin.domain.base.admin.entity.TokenState;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.Cookie; import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Date; import java.util.Date;
@Component @Component
@RequiredArgsConstructor
public class JwtTokenProvider { public class JwtTokenProvider {
private final JwtTokenService jwtTokenService;
// 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) {
Cookie accessTokenCookie = createCookieWithToken(username, JwtTokenProvider.JwtTokenType.ACCESS);
response.addCookie(accessTokenCookie);
Cookie refreshTokenCookie = createCookieWithToken(username, JwtTokenProvider.JwtTokenType.REFRESH);
response.addCookie(refreshTokenCookie);
jwtTokenService.save(
JwtTokenRequest.of(
request.getRemoteAddr(),
accessTokenCookie.getValue(),
refreshTokenCookie.getValue(),
LocalDateTime.now().plusSeconds(refreshTokenCookie.getMaxAge()),
TokenState.NORMAL,
username
)
);
}
public enum JwtTokenType { public enum JwtTokenType {
ACCESS, REFRESH ACCESS, REFRESH
} }
public Cookie createCookieWithToken(String username, JwtTokenType type) { private Cookie createCookieWithToken(String username, JwtTokenType type) {
String tokenName; String tokenName;
String subject; String subject;
long expirationTime; long expirationTime;
@ -45,16 +74,6 @@ public class JwtTokenProvider {
return tokenCookie; return tokenCookie;
} }
private Cookie createCookieWithToken(String tokenName, String subject, String username, long expirationTime) {
String token = generateToken(JwtTokenConstants.ISSUER, subject, username, expirationTime);
Cookie tokenCookie = new Cookie(tokenName, token);
tokenCookie.setHttpOnly(true);
tokenCookie.setPath("/");
return tokenCookie;
}
private String generateToken(String issuer, String subject, String username, long expirationTime) { private String generateToken(String issuer, String subject, String username, long expirationTime) {
return Jwts.builder() return Jwts.builder()
.issuer(issuer) .issuer(issuer)

View File

@ -56,11 +56,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);
Cookie accessTokenCookie = jwtTokenProvider.createCookieWithToken(username, JwtTokenProvider.JwtTokenType.ACCESS); jwtTokenProvider.generateToken(request, response, username);
response.addCookie(accessTokenCookie);
Cookie refreshTokenCookie = jwtTokenProvider.createCookieWithToken(username, JwtTokenProvider.JwtTokenType.REFRESH);
response.addCookie(refreshTokenCookie);
setSecurityContext(username); setSecurityContext(username);
} catch (JwtTokenExpiredException e) { } catch (JwtTokenExpiredException e) {