login token
This commit is contained in:
parent
b2e86d5350
commit
1d3f7d7ddc
@ -21,10 +21,14 @@ import io.company.localhost.common.security.handler.MemberAuthSuccessHandler;
|
||||
import io.company.localhost.common.security.handler.RestAccessDeniedHandler;
|
||||
import io.company.localhost.common.security.handler.RestAuthenticationEntryPointHandler;
|
||||
import io.company.localhost.common.security.service.CustomRememberMeServices;
|
||||
import io.company.localhost.common.security.service.TokenService;
|
||||
import io.company.localhost.common.security.service.MemberPrincipalDetailService;
|
||||
import io.company.localhost.common.security.session.AuthenticationSessionControlStrategy;
|
||||
import io.company.localhost.common.security.session.CustomSessionRegistryImpl;
|
||||
import io.company.localhost.service.NetmemberService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
@ -52,6 +56,7 @@ public class SecurityConfig {
|
||||
private final MemberPrincipalDetailService userDetailsService;
|
||||
private final MemberAuthSuccessHandler successHandler;
|
||||
private final MemberAuthFailureHandler failureHandler;
|
||||
private final NetmemberService netmemberService;
|
||||
private final AuthorizationManager<RequestAuthorizationContext> authorizationManager;
|
||||
|
||||
// 세션 관련 상수 설정
|
||||
@ -64,6 +69,9 @@ public class SecurityConfig {
|
||||
final String SECURITY_BASE_URL = "/api/user";
|
||||
final String LOGIN_URL = SECURITY_BASE_URL + "/login";
|
||||
|
||||
final String LOGIN_KEY = "loginSecretKey";
|
||||
|
||||
|
||||
// 보안 필터 체인 설정
|
||||
@Bean
|
||||
public SecurityFilterChain restSecurityFilterChain(HttpSecurity http, WebCorsFilter webCorsFilter) throws Exception {
|
||||
@ -115,9 +123,15 @@ public class SecurityConfig {
|
||||
|
||||
@Bean
|
||||
public RememberMeServices rememberMeServices(){
|
||||
return new CustomRememberMeServices(REMEMBER_KEY , userDetailsService);
|
||||
return new CustomRememberMeServices(tokenService(), userDetailsService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TokenService tokenService() {
|
||||
return new TokenService(REMEMBER_KEY, LOGIN_KEY);
|
||||
}
|
||||
|
||||
|
||||
// 세션 관리
|
||||
protected ConcurrentSessionControlAuthenticationStrategy sessionControlStrategy() {
|
||||
AuthenticationSessionControlStrategy sessionControlStrategy = new AuthenticationSessionControlStrategy(sessionRegistry());
|
||||
|
||||
@ -16,10 +16,15 @@ package io.company.localhost.common.security.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.company.localhost.common.dto.ApiResponse;
|
||||
import io.company.localhost.common.security.service.TokenService;
|
||||
import io.company.localhost.service.NetmemberService;
|
||||
import io.company.localhost.vo.MemberVo;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@ -31,12 +36,35 @@ import java.io.IOException;
|
||||
|
||||
@Slf4j
|
||||
@Component("successHandler")
|
||||
public class MemberAuthSuccessHandler implements AuthenticationSuccessHandler{
|
||||
public class MemberAuthSuccessHandler implements AuthenticationSuccessHandler {
|
||||
|
||||
private final TokenService tokenService;
|
||||
private final NetmemberService netmemberService;
|
||||
|
||||
public MemberAuthSuccessHandler(@Lazy TokenService tokenService, NetmemberService netmemberService) {
|
||||
this.tokenService = tokenService;
|
||||
this.netmemberService = netmemberService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authentication) throws IOException {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
// 로그인 성공한 사용자 가져오기
|
||||
Object principal = authentication.getPrincipal();
|
||||
if (!(principal instanceof MemberVo member)) {
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
return;
|
||||
}
|
||||
|
||||
// 로그인 토큰 생성
|
||||
String username = member.getLoginId();
|
||||
String loginToken = tokenService.generateToken(username, "login");
|
||||
|
||||
// DB에 저장
|
||||
netmemberService.updateMemberToken(username, loginToken);
|
||||
|
||||
response.setStatus(HttpStatus.OK.value());
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
|
||||
|
||||
@ -10,11 +10,12 @@
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 24.12.06 조인제 최초 생성
|
||||
*
|
||||
* 24.02.03 박지윤 토큰 로직 변경
|
||||
*************************************************************/
|
||||
package io.company.localhost.common.security.service;
|
||||
|
||||
import io.company.localhost.common.security.details.MemberPrincipalDetails;
|
||||
import io.company.localhost.service.NetmemberService;
|
||||
import io.company.localhost.vo.MemberVo;
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
@ -27,8 +28,6 @@ import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.authentication.RememberMeServices;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
@ -39,11 +38,12 @@ public class CustomRememberMeServices implements RememberMeServices {
|
||||
private static final long TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * 365; // 1 year
|
||||
private static final String DELIMITER = ":";
|
||||
|
||||
private final String secretKey;
|
||||
|
||||
private final TokenService tokenService;
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
public CustomRememberMeServices(String secretKey, UserDetailsService userDetailsService) {
|
||||
this.secretKey = secretKey;
|
||||
public CustomRememberMeServices(TokenService tokenService, UserDetailsService userDetailsService) {
|
||||
this.tokenService = tokenService;
|
||||
this.userDetailsService = userDetailsService;
|
||||
}
|
||||
|
||||
@ -55,33 +55,22 @@ public class CustomRememberMeServices implements RememberMeServices {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] tokenParts = decodeAndSplitCookie(rememberMeCookie.getValue());
|
||||
if (tokenParts == null || tokenParts.length != 3) {
|
||||
String token = rememberMeCookie.getValue();
|
||||
|
||||
if (!tokenService.validateToken(token, "rememberme")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String[] tokenParts = decodeAndSplitCookie(token);
|
||||
String username = tokenParts[0];
|
||||
long expiryTime;
|
||||
try {
|
||||
expiryTime = Long.parseLong(tokenParts[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String signature = tokenParts[2];
|
||||
if (!isTokenValid(username, expiryTime, signature)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() > expiryTime) {
|
||||
return null;
|
||||
}
|
||||
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
if (userDetails != null) {
|
||||
MemberPrincipalDetails memberDetails = (MemberPrincipalDetails) userDetails;
|
||||
MemberVo memberVo = memberDetails.member();
|
||||
RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(secretKey, memberVo, userDetails.getAuthorities());
|
||||
|
||||
String rememberMeSecretKey = tokenService.getRememberMeSecretKey();
|
||||
RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(rememberMeSecretKey, memberVo, userDetails.getAuthorities());
|
||||
auth.setAuthenticated(true);
|
||||
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||
return auth;
|
||||
@ -104,10 +93,11 @@ public class CustomRememberMeServices implements RememberMeServices {
|
||||
}
|
||||
|
||||
String username = member.getLoginId();
|
||||
long expiryTime = System.currentTimeMillis() + (TOKEN_VALIDITY_SECONDS * 1000);
|
||||
String signature = generateSignature(username, expiryTime);
|
||||
String tokenValue = encodeToken(username, expiryTime, signature);
|
||||
String tokenValue = tokenService.generateToken(username, "rememberme");
|
||||
String loginToken = tokenService.generateToken(username, "login");
|
||||
|
||||
// DB에 저장
|
||||
// netmemberService.updateMemberToken(username, loginToken);
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(successfulAuthentication);
|
||||
|
||||
@ -149,24 +139,4 @@ public class CustomRememberMeServices implements RememberMeServices {
|
||||
}
|
||||
}
|
||||
|
||||
private String encodeToken(String username, long expiryTime, String signature) {
|
||||
String tokenValue = username + DELIMITER + expiryTime + DELIMITER + signature;
|
||||
return Base64.getEncoder().encodeToString(tokenValue.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private boolean isTokenValid(String username, long expiryTime, String signature) {
|
||||
String expectedSignature = generateSignature(username, expiryTime);
|
||||
return expectedSignature.equals(signature);
|
||||
}
|
||||
|
||||
private String generateSignature(String username, long expiryTime) {
|
||||
try {
|
||||
String data = username + DELIMITER + expiryTime;
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
|
||||
return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to generate signature", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,121 @@
|
||||
/************************************************************
|
||||
*
|
||||
* @packageName : io.company.localhost.common.security.service
|
||||
* @fileName : TokenService.java
|
||||
* @author : 박지윤
|
||||
* @date : 24.01.24
|
||||
* @description :
|
||||
*
|
||||
* ===========================================================
|
||||
* DATE AUTHOR NOTE
|
||||
* -----------------------------------------------------------
|
||||
* 24.01.24 박지윤 최초 생성
|
||||
*************************************************************/
|
||||
package io.company.localhost.common.security.service;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class TokenService {
|
||||
private static final long REMBER_TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * 365; // 1년
|
||||
private static final long LOGIN_TOKEN_VALIDITY_SECONDS = 60 * 60 * 24 * 365; // 하루
|
||||
private static final String DELIMITER = ":";
|
||||
private final String rememberMeSecretKey;
|
||||
private final String loginSecretKey;
|
||||
|
||||
public TokenService(String rememberMeSecretKey, String loginSecretKey) {
|
||||
this.rememberMeSecretKey = rememberMeSecretKey;
|
||||
this.loginSecretKey = loginSecretKey;
|
||||
}
|
||||
|
||||
public String generateToken(String username, String tokenType) {
|
||||
long expiryTime;
|
||||
|
||||
// 토큰타입에 따라 만료기간 다르게 설정
|
||||
if ("rememberme".equals(tokenType)) {
|
||||
expiryTime = System.currentTimeMillis() + (REMBER_TOKEN_VALIDITY_SECONDS * 1000);
|
||||
} else if ("login".equals(tokenType)) {
|
||||
expiryTime = System.currentTimeMillis() + (LOGIN_TOKEN_VALIDITY_SECONDS * 1000);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid token type");
|
||||
}
|
||||
|
||||
String signature = generateSignature(username, expiryTime, tokenType);
|
||||
return encodeToken(username, expiryTime, signature);
|
||||
}
|
||||
|
||||
public boolean validateToken(String token, String tokenType) {
|
||||
try {
|
||||
String[] tokenParts = decodeAndSplitToken(token);
|
||||
if (tokenParts == null || tokenParts.length != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String username = tokenParts[0];
|
||||
long expiryTime;
|
||||
try {
|
||||
expiryTime = Long.parseLong(tokenParts[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String signature = tokenParts[2];
|
||||
if (!isTokenValid(username, expiryTime, signature, tokenType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return System.currentTimeMillis() <= expiryTime;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String generateSignature(String username, long expiryTime, String tokenType) {
|
||||
try {
|
||||
// tokenType에 따라 login 또는 remember secretKey 사용
|
||||
String secretKeyToUse = "rememberme".equals(tokenType) ? rememberMeSecretKey : loginSecretKey;
|
||||
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(new SecretKeySpec(secretKeyToUse.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
|
||||
String data = username + DELIMITER + expiryTime;
|
||||
return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to generate signature", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String encodeToken(String username, long expiryTime, String signature) {
|
||||
String tokenValue = username + DELIMITER + expiryTime + DELIMITER + signature;
|
||||
return Base64.getEncoder().encodeToString(tokenValue.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private String[] decodeAndSplitToken(String token) {
|
||||
try {
|
||||
String decodedValue = new String(Base64.getDecoder().decode(token), StandardCharsets.UTF_8);
|
||||
return decodedValue.split(DELIMITER);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTokenValid(String username, long expiryTime, String signature, String tokenType) {
|
||||
String expectedSignature = generateSignature(username, expiryTime, tokenType);
|
||||
return expectedSignature.equals(signature);
|
||||
}
|
||||
|
||||
public String getRememberMeSecretKey() {
|
||||
return rememberMeSecretKey;
|
||||
}
|
||||
|
||||
}
|
||||
@ -26,6 +26,8 @@ public interface NetmemberMapper {
|
||||
|
||||
MemberVo findByLoginId(String id);
|
||||
|
||||
int updateMemberToken(String id, String token);
|
||||
|
||||
int insertMember(MapDto map);
|
||||
|
||||
int selectCheckId(String memberIds);
|
||||
|
||||
@ -19,6 +19,7 @@ import java.util.List;
|
||||
|
||||
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.company.localhost.common.dto.MapDto;
|
||||
@ -28,7 +29,7 @@ import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class netmemberService {
|
||||
public class NetmemberService {
|
||||
private final NetmemberMapper memberMapper;
|
||||
private final commoncodMapper commoncodMapper;
|
||||
private final DelegatingPasswordEncoder passwordEncoder;
|
||||
@ -79,6 +80,7 @@ public class netmemberService {
|
||||
public boolean selectCheckId(String memberIds) {
|
||||
return memberMapper.selectCheckId(memberIds) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 사원 목록 전체 조회
|
||||
*
|
||||
@ -89,4 +91,9 @@ public class netmemberService {
|
||||
return memberMapper.getallUserList();
|
||||
}
|
||||
|
||||
|
||||
@Transactional
|
||||
public void updateMemberToken(String id, String token) {
|
||||
memberMapper.updateMemberToken(id, token);
|
||||
}
|
||||
}
|
||||
@ -32,6 +32,13 @@
|
||||
MEMBERIDS = #{id}
|
||||
</select>
|
||||
|
||||
<update id="updateMemberToken">
|
||||
UPDATE netmember
|
||||
SET MEMBERTKN = #{token}
|
||||
WHERE MEMBERIDS = #{id}
|
||||
</update>
|
||||
|
||||
|
||||
<!-- 회원가입 -->
|
||||
<insert id="insertMember" useGeneratedKeys="true" keyProperty="MEMBERSEQ">
|
||||
INSERT INTO netmember (
|
||||
@ -87,6 +94,7 @@
|
||||
FROM netmember
|
||||
WHERE MEMBERIDS = #{memberIds}
|
||||
</select>
|
||||
|
||||
<select id="getallUserList" resultType="Map">
|
||||
SELECT *
|
||||
FROM
|
||||
|
||||
Loading…
Reference in New Issue
Block a user