parent
1e4eee368b
commit
3ca0b7eae6
@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
|
||||
<mapper namespace="kr.xit.core.biz.mapper.IAuthApiMapper">
|
||||
<!-- 일반 로그인 -->
|
||||
<select id="actionLogin" resultType="egovframework.com.cmm.LoginVO">
|
||||
<if test="userSe = 'USR'">
|
||||
/** auth-mysql-mapper|actionLogin-로그인|julim */
|
||||
SELECT user_id AS id
|
||||
, user_nm AS name
|
||||
, password
|
||||
, ihidnum
|
||||
, email_adres AS email
|
||||
, 'USR' AS userSe
|
||||
, orgnzt_id
|
||||
, esntl_id
|
||||
FROM xit_user_info
|
||||
WHERE user_id = #{id}
|
||||
AND password = #{password}
|
||||
AND user_sttus_code = 'P'
|
||||
|
||||
</if>
|
||||
</select>
|
||||
|
||||
</mapper>
|
@ -1,245 +0,0 @@
|
||||
package egovframework.com.cmm;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @Class Name : LoginVO.java
|
||||
* @Description : Login VO class
|
||||
* @Modification Information
|
||||
* @
|
||||
* @ 수정일 수정자 수정내용
|
||||
* @ ------- -------- ---------------------------
|
||||
* @ 2009.03.03 박지욱 최초 생성
|
||||
*
|
||||
* @author 공통서비스 개발팀 박지욱
|
||||
* @since 2009.03.03
|
||||
* @version 1.0
|
||||
* @see
|
||||
*
|
||||
*/
|
||||
public class LoginVO implements Serializable{
|
||||
|
||||
/** 아이디 */
|
||||
private String id;
|
||||
/** 이름 */
|
||||
private String name;
|
||||
/** 주민등록번호 */
|
||||
private String ihidNum;
|
||||
/** 이메일주소 */
|
||||
private String email;
|
||||
/** 비밀번호 */
|
||||
private String password;
|
||||
/** 비밀번호 힌트 */
|
||||
private String passwordHint;
|
||||
/** 비밀번호 정답 */
|
||||
private String passwordCnsr;
|
||||
/** 사용자구분 */
|
||||
private String userSe;
|
||||
/** 조직(부서)ID */
|
||||
private String orgnztId;
|
||||
/** 조직(부서)명 */
|
||||
private String orgnztNm;
|
||||
/** 고유아이디 */
|
||||
private String uniqId;
|
||||
/** 로그인 후 이동할 페이지 */
|
||||
private String url;
|
||||
/** 사용자 IP정보 */
|
||||
private String ip;
|
||||
/** GPKI인증 DN */
|
||||
private String dn;
|
||||
/**
|
||||
* id attribute 를 리턴한다.
|
||||
* @return String
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
/**
|
||||
* id attribute 값을 설정한다.
|
||||
* @param id String
|
||||
*/
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
/**
|
||||
* name attribute 를 리턴한다.
|
||||
* @return String
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
/**
|
||||
* name attribute 값을 설정한다.
|
||||
* @param name String
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
/**
|
||||
* ihidNum attribute 를 리턴한다.
|
||||
* @return String
|
||||
*/
|
||||
public String getIhidNum() {
|
||||
return ihidNum;
|
||||
}
|
||||
/**
|
||||
* ihidNum attribute 값을 설정한다.
|
||||
* @param ihidNum String
|
||||
*/
|
||||
public void setIhidNum(String ihidNum) {
|
||||
this.ihidNum = ihidNum;
|
||||
}
|
||||
/**
|
||||
* email attribute 를 리턴한다.
|
||||
* @return String
|
||||
*/
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
/**
|
||||
* email attribute 값을 설정한다.
|
||||
* @param email String
|
||||
*/
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
/**
|
||||
* password attribute 를 리턴한다.
|
||||
* @return String
|
||||
*/
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
/**
|
||||
* password attribute 값을 설정한다.
|
||||
* @param password String
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
/**
|
||||
* passwordHint attribute 를 리턴한다.
|
||||
* @return String
|
||||
*/
|
||||
public String getPasswordHint() {
|
||||
return passwordHint;
|
||||
}
|
||||
/**
|
||||
* passwordHint attribute 값을 설정한다.
|
||||
* @param passwordHint String
|
||||
*/
|
||||
public void setPasswordHint(String passwordHint) {
|
||||
this.passwordHint = passwordHint;
|
||||
}
|
||||
/**
|
||||
* passwordCnsr attribute 를 리턴한다.
|
||||
* @return String
|
||||
*/
|
||||
public String getPasswordCnsr() {
|
||||
return passwordCnsr;
|
||||
}
|
||||
/**
|
||||
* passwordCnsr attribute 값을 설정한다.
|
||||
* @param passwordCnsr String
|
||||
*/
|
||||
public void setPasswordCnsr(String passwordCnsr) {
|
||||
this.passwordCnsr = passwordCnsr;
|
||||
}
|
||||
/**
|
||||
* userSe attribute 를 리턴한다.
|
||||
* @return String
|
||||
*/
|
||||
public String getUserSe() {
|
||||
return userSe;
|
||||
}
|
||||
/**
|
||||
* userSe attribute 값을 설정한다.
|
||||
* @param userSe String
|
||||
*/
|
||||
public void setUserSe(String userSe) {
|
||||
this.userSe = userSe;
|
||||
}
|
||||
/**
|
||||
* orgnztId attribute 를 리턴한다.
|
||||
* @return String
|
||||
*/
|
||||
public String getOrgnztId() {
|
||||
return orgnztId;
|
||||
}
|
||||
/**
|
||||
* orgnztId attribute 값을 설정한다.
|
||||
* @param orgnztId String
|
||||
*/
|
||||
public void setOrgnztId(String orgnztId) {
|
||||
this.orgnztId = orgnztId;
|
||||
}
|
||||
/**
|
||||
* uniqId attribute 를 리턴한다.
|
||||
* @return String
|
||||
*/
|
||||
public String getUniqId() {
|
||||
return uniqId;
|
||||
}
|
||||
/**
|
||||
* uniqId attribute 값을 설정한다.
|
||||
* @param uniqId String
|
||||
*/
|
||||
public void setUniqId(String uniqId) {
|
||||
this.uniqId = uniqId;
|
||||
}
|
||||
/**
|
||||
* url attribute 를 리턴한다.
|
||||
* @return String
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
/**
|
||||
* url attribute 값을 설정한다.
|
||||
* @param url String
|
||||
*/
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
/**
|
||||
* ip attribute 를 리턴한다.
|
||||
* @return String
|
||||
*/
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
/**
|
||||
* ip attribute 값을 설정한다.
|
||||
* @param ip String
|
||||
*/
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
/**
|
||||
* dn attribute 를 리턴한다.
|
||||
* @return String
|
||||
*/
|
||||
public String getDn() {
|
||||
return dn;
|
||||
}
|
||||
/**
|
||||
* dn attribute 값을 설정한다.
|
||||
* @param dn String
|
||||
*/
|
||||
public void setDn(String dn) {
|
||||
this.dn = dn;
|
||||
}
|
||||
/**
|
||||
* @return the orgnztNm
|
||||
*/
|
||||
public String getOrgnztNm() {
|
||||
return orgnztNm;
|
||||
}
|
||||
/**
|
||||
* @param orgnztNm the orgnztNm to set
|
||||
*/
|
||||
public void setOrgnztNm(String orgnztNm) {
|
||||
this.orgnztNm = orgnztNm;
|
||||
}
|
||||
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
package egovframework.com.jwt;
|
||||
|
||||
import egovframework.com.cmm.LoginVO;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import java.io.Serializable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import kr.xit.core.consts.ErrorCode;
|
||||
import kr.xit.core.exception.BizRuntimeException;
|
||||
import kr.xit.core.spring.config.properties.JwtProperties;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
//security 관련 제외한 jwt util 클래스
|
||||
|
||||
@Component
|
||||
public class EgovJwtTokenUtil implements Serializable{
|
||||
|
||||
private Key key;
|
||||
|
||||
private final transient JwtProperties jwtProp;
|
||||
|
||||
public EgovJwtTokenUtil(@Value("${app.token.secretKey}")String secret, JwtProperties jwtProperties) {
|
||||
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||
this.jwtProp = jwtProperties;
|
||||
}
|
||||
|
||||
public String getUserIdFromToken(String token) {
|
||||
Claims claims = getClaimFromToken(token);
|
||||
return claims.get("id").toString();
|
||||
}
|
||||
|
||||
public String getUserSeFromToken(String token) {
|
||||
Claims claims = getClaimFromToken(token);
|
||||
return claims.get("userSe").toString();
|
||||
}
|
||||
public String getInfoFromToken(String type, String token) {
|
||||
Claims claims = getClaimFromToken(token);
|
||||
return claims.get(type).toString();
|
||||
}
|
||||
|
||||
public Claims getClaimFromToken(String token) {
|
||||
final Claims claims = getAllClaimsFromToken(token);
|
||||
return claims;
|
||||
}
|
||||
|
||||
//retrieve username from jwt token
|
||||
public String getUsernameFromToken(String token) {
|
||||
return getClaimFromToken(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
//retrieve expiration date from jwt token
|
||||
public Date getExpirationDateFromToken(String token) {
|
||||
return getClaimFromToken(token, Claims::getExpiration);
|
||||
}
|
||||
|
||||
//generate token for user
|
||||
public String generateToken(LoginVO loginVO) {
|
||||
return doGenerateToken(new HashMap<>(), loginVO.getUserSe()+loginVO.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param loginVO LoginVO
|
||||
* @param claims Map<String,Object> JWT Payload(Claims)에 해당 속성 추가
|
||||
* @return
|
||||
*/
|
||||
public String generateToken(LoginVO loginVO, Map<String, Object> claims) {
|
||||
return doGenerateToken(claims, loginVO.getUserSe()+loginVO.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰 유효성 검증
|
||||
*
|
||||
* @param token
|
||||
* @param loginVO
|
||||
* @return
|
||||
*/
|
||||
public Boolean validateToken(String token, LoginVO loginVO) {
|
||||
final String username = getUsernameFromToken(token);
|
||||
if(!username.equals(loginVO.getUserSe()+loginVO.getId())){
|
||||
throw BizRuntimeException.create(ErrorCode.INVALID_TOKEN);
|
||||
}
|
||||
return isTokenExpired(token);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------------------
|
||||
|
||||
//while creating the token -
|
||||
//1. Define claims of the token, like Issuer, Expiration, Subject, and the ID
|
||||
//2. Sign the JWT using the HS512 algorithm and secret key.
|
||||
//3. According to JWS Compact Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1)
|
||||
// compaction of the JWT to a URL-safe string
|
||||
private String doGenerateToken(final Map<String, Object> claimMap, final String subject) {
|
||||
Instant now = new Date().toInstant();
|
||||
|
||||
// payload(claimMap)에 시스템 속성 추가
|
||||
Claims claims = Jwts.claims(claimMap)
|
||||
.setIssuer(jwtProp.getIssuer())
|
||||
.setAudience(jwtProp.getAudience())
|
||||
.setSubject(subject)
|
||||
.setIssuedAt(Date.from(now))
|
||||
.setExpiration(Date.from(now.plus(jwtProp.getTokenExpiry(), ChronoUnit.DAYS)));
|
||||
|
||||
return Jwts.builder()
|
||||
// 2. Signature
|
||||
.signWith(key, SignatureAlgorithm.valueOf(jwtProp.getAlg())) // header "alg": "HS512"
|
||||
.setHeaderParam("typ", jwtProp.getTyp())
|
||||
.setHeaderParam("alg", jwtProp.getAlg())
|
||||
|
||||
// 1. Payload claim
|
||||
.setClaims(claims)
|
||||
|
||||
// 3. JWT compact
|
||||
.compact();
|
||||
}
|
||||
|
||||
private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
|
||||
final Claims claims = getAllClaimsFromToken(token);
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
|
||||
//for retrieveing any information from token we will need the secret key
|
||||
private Claims getAllClaimsFromToken(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(key)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
//check if the token has expired
|
||||
private Boolean isTokenExpired(String token) {
|
||||
final Date expiration = getExpirationDateFromToken(token);
|
||||
if(expiration.before(new Date())) throw BizRuntimeException.create(ErrorCode.EXPIRED_TOKEN);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package kr.xit.core.model;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class RefreshTokenDTO {
|
||||
private final String id;
|
||||
private final String refreshToken;
|
||||
private final Long refreshTokenExpiresIn;
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package kr.xit.core.model;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class TokenDTO {
|
||||
private final String grantType;
|
||||
private final String authorizationType;
|
||||
private final String accessToken;
|
||||
private final String refreshToken;
|
||||
private final Long accessTokenExpiresIn;
|
||||
private final Long refreshTokenExpiresIn;
|
||||
}
|
@ -0,0 +1,224 @@
|
||||
package kr.xit.core.spring.auth.jwt;
|
||||
|
||||
import egovframework.com.cmm.model.LoginVO;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import io.jsonwebtoken.io.Decoders;
|
||||
import io.jsonwebtoken.io.Encoders;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import kr.xit.core.consts.Constants.JwtToken;
|
||||
import kr.xit.core.consts.ErrorCode;
|
||||
import kr.xit.core.exception.BizRuntimeException;
|
||||
import kr.xit.core.model.TokenDTO;
|
||||
import kr.xit.core.spring.config.properties.JwtProperties;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : JWT 토큰 생성, 검증
|
||||
* packageName : kr.xit.core.spring.auth
|
||||
* fileName : JwtTokenProvider
|
||||
* author : julim
|
||||
* date : 2023-11-29
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-11-29 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class JwtTokenProvider {
|
||||
public static final String REFRESH_HEADER = "Refresh";
|
||||
private Key key;
|
||||
private final transient JwtProperties jwtProp;
|
||||
// Bean 등록후 Key SecretKey HS256 decode
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
String base64EncodedSecretKey = encodeBase64SecretKey(jwtProp.getSecretKey());
|
||||
this.key = getKeyFromBase64EncodedKey(base64EncodedSecretKey);
|
||||
}
|
||||
|
||||
public String encodeBase64SecretKey(String secretKey) {
|
||||
return Encoders.BASE64.encode(secretKey.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
private Key getKeyFromBase64EncodedKey(String base64EncodedSecretKey) {
|
||||
byte[] keyBytes = Decoders.BASE64.decode(base64EncodedSecretKey);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
|
||||
public TokenDTO generateTokenDto(final Map<String, Object> claims, final String subject) {
|
||||
Instant now = new Date().toInstant();
|
||||
|
||||
Date accessTokenExpiresIn = getTokenExpiration(jwtProp.getTokenExpiry(), now);
|
||||
Date refreshTokenExpiresIn = getTokenExpiration(jwtProp.getRefreshTokenExpiry(), now);
|
||||
|
||||
String accessToken = Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setSubject(subject)
|
||||
.setExpiration(accessTokenExpiresIn)
|
||||
.setIssuedAt(Date.from(now))
|
||||
.signWith(key, SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
|
||||
String refreshToken = Jwts.builder()
|
||||
.setSubject(subject)
|
||||
.setIssuedAt(Date.from(now))
|
||||
.setExpiration(refreshTokenExpiresIn)
|
||||
.signWith(key)
|
||||
.compact();
|
||||
|
||||
return TokenDTO.builder()
|
||||
.grantType(JwtToken.GRANT_TYPE.getCode())
|
||||
.authorizationType(JwtToken.HEADER_NAME.getCode())
|
||||
.accessToken(accessToken)
|
||||
.accessTokenExpiresIn(accessTokenExpiresIn.getTime())
|
||||
.refreshToken(refreshToken)
|
||||
.build();
|
||||
}
|
||||
|
||||
// JWT 토큰을 복호화하여 토큰 정보를 반환
|
||||
// public Authentication getAuthentication(String accessToken) {
|
||||
// Claims claims = parseClaims(accessToken);
|
||||
//
|
||||
// if (claims.get("role") == null) {
|
||||
// throw new BusinessLogicException(ExceptionCode.NO_ACCESS_TOKEN);
|
||||
// }
|
||||
//
|
||||
// String authority = claims.get("role").toString();
|
||||
//
|
||||
// CustomUserDetails customUserDetails = CustomUserDetails.of(
|
||||
// claims.getSubject(),
|
||||
// authority);
|
||||
//
|
||||
//
|
||||
// log.info("# AuthMember.getRoles 권한 체크 = {}", customUserDetails.getAuthorities().toString());
|
||||
//
|
||||
// return new UsernamePasswordAuthenticationToken(customUserDetails, null, customUserDetails.getAuthorities());
|
||||
// }
|
||||
|
||||
// 토큰 검증
|
||||
public boolean validateToken(String token, HttpServletResponse response) {
|
||||
try {
|
||||
parseClaims(token);
|
||||
} catch (MalformedJwtException e) {
|
||||
log.info("Invalid JWT token");
|
||||
log.trace("Invalid JWT token trace = {}", e);
|
||||
throw BizRuntimeException.of("fail.jwt.invalid");
|
||||
} catch (ExpiredJwtException e) {
|
||||
log.info("Expired JWT token");
|
||||
log.trace("Expired JWT token trace = {}", e);
|
||||
throw BizRuntimeException.of("fail.jwt.expired");
|
||||
} catch (UnsupportedJwtException e) {
|
||||
log.info("Unsupported JWT token");
|
||||
log.trace("Unsupported JWT token trace = {}", e);
|
||||
throw BizRuntimeException.of("fail.jwt.unsupported");
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.info("JWT claims string is empty.");
|
||||
log.trace("JWT claims string is empty trace = {}", e);
|
||||
throw BizRuntimeException.of("fail.jwt.illegalArgument");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Date getTokenExpiration(final int expirationDay, final Instant now) {
|
||||
return Date.from(now.plus(expirationDay, ChronoUnit.DAYS));
|
||||
}
|
||||
|
||||
// Token 복호화 및 예외 발생(토큰 만료, 시그니처 오류)시 Claims 객체가 안만들어짐.
|
||||
public Claims parseClaims(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(key)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
public void accessTokenSetHeader(String accessToken, HttpServletResponse response) {
|
||||
String headerValue = JwtToken.GRANT_TYPE + accessToken;
|
||||
response.setHeader(JwtToken.HEADER_NAME.getCode(), headerValue);
|
||||
}
|
||||
|
||||
public void refresshTokenSetHeader(String refreshToken, HttpServletResponse response) {
|
||||
response.setHeader("Refresh", refreshToken);
|
||||
}
|
||||
|
||||
// Request Header에 Access Token 정보를 추출하는 메서드
|
||||
public String resolveAccessToken(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader(JwtToken.HEADER_NAME.getCode());
|
||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(JwtToken.GRANT_TYPE.getCode())) {
|
||||
return bearerToken.substring(JwtToken.GRANT_TYPE.getCode().length()+1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Request Header에 Refresh Token 정보를 추출하는 메서드
|
||||
public String resolveRefreshToken(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader(REFRESH_HEADER);
|
||||
if (StringUtils.hasText(bearerToken)) {
|
||||
return bearerToken;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Date getExpirationDateFromToken(String token) {
|
||||
return getClaimFromToken(token, Claims::getExpiration);
|
||||
}
|
||||
|
||||
public String getUserIdFromToken(String token) {
|
||||
Claims claims = parseClaims(token);
|
||||
return claims.get("id").toString();
|
||||
}
|
||||
|
||||
public String getUsernameFromToken(String token) {
|
||||
return getClaimFromToken(token, Claims::getSubject);
|
||||
}
|
||||
|
||||
public String getUserSeFromToken(String token) {
|
||||
Claims claims = parseClaims(token);
|
||||
return claims.get("userSe").toString();
|
||||
}
|
||||
public String getInfoFromToken(String type, String token) {
|
||||
Claims claims = parseClaims(token);
|
||||
return claims.get(type).toString();
|
||||
}
|
||||
|
||||
public Boolean validateToken(String token, LoginVO loginVO) {
|
||||
final String username = getUsernameFromToken(token);
|
||||
if(!username.equals(loginVO.getUserSe()+loginVO.getId())){
|
||||
throw BizRuntimeException.create(ErrorCode.INVALID_TOKEN);
|
||||
}
|
||||
return isTokenExpired(token);
|
||||
}
|
||||
|
||||
private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
|
||||
final Claims claims = parseClaims(token);
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
|
||||
private Boolean isTokenExpired(String token) {
|
||||
final Date expiration = getExpirationDateFromToken(token);
|
||||
if(expiration.before(new Date())) throw BizRuntimeException.create(ErrorCode.EXPIRED_TOKEN);
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package kr.xit.core.spring.util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import kr.xit.core.exception.BizRuntimeException;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : AES128 암호화 및 복호화 기능을 하는 AES128Config class
|
||||
* packageName : kr.xit.core.spring.util
|
||||
* fileName : AES128Config
|
||||
* author : julim
|
||||
* date : 2023-11-29
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-11-29 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Component
|
||||
public class AES128Config {
|
||||
private static final Charset ENCODING_TYPE = StandardCharsets.UTF_8;
|
||||
private static final String INSTANCE_TYPE = "AES/CBC/PKCS5Padding";
|
||||
|
||||
@Value("${app.aes.secret-key:}")
|
||||
private String secretKey;
|
||||
private IvParameterSpec ivParameterSpec;
|
||||
private SecretKeySpec secretKeySpec;
|
||||
private Cipher cipher;
|
||||
|
||||
@PostConstruct
|
||||
public void init() throws NoSuchPaddingException, NoSuchAlgorithmException {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
byte[] iv = new byte[16]; // 16bytes = 128bits
|
||||
secureRandom.nextBytes(iv);
|
||||
ivParameterSpec = new IvParameterSpec(iv);
|
||||
secretKeySpec = new SecretKeySpec(secretKey.getBytes(ENCODING_TYPE), "AES");
|
||||
cipher = Cipher.getInstance(INSTANCE_TYPE);
|
||||
}
|
||||
|
||||
// AES 암호화
|
||||
|
||||
/**
|
||||
* AES 암호화
|
||||
* @param plaintext String
|
||||
* @return String
|
||||
*/
|
||||
public String encryptAes(String plaintext) {
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
|
||||
byte[] encryted = cipher.doFinal(plaintext.getBytes(ENCODING_TYPE));
|
||||
return new String(Base64.getEncoder().encode(encryted), ENCODING_TYPE);
|
||||
} catch (Exception e) {
|
||||
throw BizRuntimeException.of("fail.aes.encode");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AES 복호화
|
||||
* @param plaintext String
|
||||
* @return String
|
||||
*/
|
||||
public String decryptAes(String plaintext) {
|
||||
try {
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
|
||||
byte[] decoded = Base64.getDecoder().decode(plaintext.getBytes(ENCODING_TYPE));
|
||||
return new String(cipher.doFinal(decoded), ENCODING_TYPE);
|
||||
} catch (Exception e) {
|
||||
throw BizRuntimeException.of("fail.aes.decode");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package kr.xit.core.spring.test;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.restdocs.RestDocumentationExtension;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* description : 통합 테스트에 공통적으로 사용할 수 있는 공통 클래스
|
||||
* <code>@Disabled</code> : 테스트 클래스 또는 테스트 메서드를 실행하지 않는다
|
||||
* -> 상속만을 위한 클래스이기 때문에 실행할 필요가 없다.
|
||||
* <code>@Transactional</code> : 각각의 테스트 메서드가 실행될 때마다, 데이터베이스를 롤백
|
||||
* -> @Rollback(false) 애너테이션을 추가시 데이타베이스 반영
|
||||
* <code>@AutoConfigureMockMvc</code> : @WebMvcTest가 아닌 @SpringBootTest 애너테이션을 사용하면서
|
||||
* MockMvc를 이용한 테스트를 해야 할 때 필요
|
||||
* <code>@ActiveProfiles</code> : 테스트 수행시 사용할 프로파일 지정
|
||||
* <code>@AutoConfigureRestDocs</code> : Spring REST Docs를 사용하기 위해 MockMvc 빈을 커스터마이즈
|
||||
* -> Mock MVC, REST Assured 또는 WebTestClient로 테스트할 때 Spring REST Docs 사용 가능
|
||||
* <code>@ExtendWith(RestDocumentationExtension.class)</code> : Spring REST Docs를 활성화하는 데 사용되는 JUNit 5 애너테이션
|
||||
* packageName : kr.xit.core.spring.test
|
||||
* fileName : BaseIntegrationTest
|
||||
* author : julim
|
||||
* date : 2023-11-29
|
||||
* ======================================================================
|
||||
* 변경일 변경자 변경 내용
|
||||
* ----------------------------------------------------------------------
|
||||
* 2023-11-29 julim 최초 생성
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
@Disabled
|
||||
@Transactional
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
@AutoConfigureRestDocs
|
||||
@ActiveProfiles("test")
|
||||
@ExtendWith(RestDocumentationExtension.class)
|
||||
public class BaseIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
protected MockMvc mockMvc;
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package kr.xit.core.spring.util;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import kr.xit.core.spring.test.BaseIntegrationTest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
|
||||
@Slf4j
|
||||
class AES128ConfigTest extends BaseIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private AES128Config aes128Config;
|
||||
|
||||
@Test
|
||||
@DisplayName("Aes128 암호화가 잘 이루어지는지 테스트")
|
||||
void aes128Test() {
|
||||
String text = "this is test";
|
||||
String enc = aes128Config.encryptAes(text);
|
||||
String dec = aes128Config.decryptAes(enc);
|
||||
log.info("enc = {}", enc);
|
||||
log.info("dec = {}", dec);
|
||||
|
||||
assertThat(dec).isEqualTo(text);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue