diff --git a/src/main/java/umc/crud/config/secret/Secret.java b/src/main/java/umc/crud/config/secret/Secret.java new file mode 100644 index 0000000..5aa7664 --- /dev/null +++ b/src/main/java/umc/crud/config/secret/Secret.java @@ -0,0 +1,7 @@ +package umc.crud.config.secret; + +public class Secret { + // 터미널에 $ openssl rand -hex 64 명령어를 입력해 랜덤 문자열 생성해 secret key로 사용 + public static String JWT_SECRET_KEY = "5fb7652d9ffd2098bcdd5434c9a9fbd5a84832a25b3897836fd50ba2f780588d4febfa3be27cd08bcd70d7ffffa8dca268a642a86a0dc838896d613fb52d3114"; + public static String USER_PASSWORD_KEY = ""; +} diff --git a/src/main/java/umc/crud/src/user/UserDao.java b/src/main/java/umc/crud/src/user/UserDao.java index 2c5ce5f..6c698d3 100644 --- a/src/main/java/umc/crud/src/user/UserDao.java +++ b/src/main/java/umc/crud/src/user/UserDao.java @@ -81,7 +81,7 @@ public int modifyUserName(PatchUserReq patchUserReq) { // 로그인: 해당 email에 해당되는 user의 암호화된 비밀번호 값을 가져온다. public User getPwd(PostLoginReq postLoginReq) { - String getPwdQuery = "select userIdx, password,email,nickname from User where email = ?"; + String getPwdQuery = "select userIdx, password, email, nickname from User where email = ?"; String getPwdParams = postLoginReq.getEmail(); // 주입될 email값을 클라이언트의 요청에서 가져온다. return this.jdbcTemplate.queryForObject(getPwdQuery, diff --git a/src/main/java/umc/crud/src/user/UserProvider.java b/src/main/java/umc/crud/src/user/UserProvider.java index 469f790..0f0c36a 100644 --- a/src/main/java/umc/crud/src/user/UserProvider.java +++ b/src/main/java/umc/crud/src/user/UserProvider.java @@ -47,7 +47,7 @@ public PostLoginRes logIn(PostLoginReq postLoginReq) throws BaseException { User user = userDao.getPwd(postLoginReq); String password; try { - password = new AES128(Secret.USER_INFO_PASSWORD_KEY).decrypt(user.getPassword()); // 암호화 + password = new AES128(Secret.USER_PASSWORD_KEY).decrypt(user.getPassword()); // 복호화 // 회원가입할 때 비밀번호가 암호화되어 저장되었기 떄문에 로그인 시애도 암호화된 값끼리 비교 } catch (Exception ignored) { throw new BaseException(PASSWORD_DECRYPTION_ERROR); diff --git a/src/main/java/umc/crud/src/user/UserService.java b/src/main/java/umc/crud/src/user/UserService.java index a999612..b5342ce 100644 --- a/src/main/java/umc/crud/src/user/UserService.java +++ b/src/main/java/umc/crud/src/user/UserService.java @@ -48,7 +48,7 @@ public PostUserRes createUser(PostUserReq postUserReq) throws BaseException { try { // 암호화: postUserReq에서 제공받은 비밀번호를 암호화해 DB에 저장 // ex) password123 -> dfhsjfkjdsnj4@!$!@chdsnjfwkenjfnsjfnjsd.fdsfaifsadjfjaf - pwd = new AES128(Secret.USER_INFO_PASSWORD_KEY).encrypt(postUserReq.getPassword()); // 암호화 + pwd = new AES128(Secret.USER_PASSWORD_KEY).encrypt(postUserReq.getPassword()); // 암호화 postUserReq.setPassword(pwd); } catch (Exception ignored) { // 암호화에 실패할 경우 에러 발생 throw new BaseException(PASSWORD_ENCRYPTION_ERROR); diff --git a/src/main/java/umc/crud/src/user/model/PostLoginRes.java b/src/main/java/umc/crud/src/user/model/PostLoginRes.java index 3d9f6a8..0767e92 100644 --- a/src/main/java/umc/crud/src/user/model/PostLoginRes.java +++ b/src/main/java/umc/crud/src/user/model/PostLoginRes.java @@ -9,4 +9,5 @@ @AllArgsConstructor public class PostLoginRes { private int userIdx; + private String jwt; } diff --git a/src/main/java/umc/crud/src/user/model/PostUserRes.java b/src/main/java/umc/crud/src/user/model/PostUserRes.java index e6d1f21..edd1708 100644 --- a/src/main/java/umc/crud/src/user/model/PostUserRes.java +++ b/src/main/java/umc/crud/src/user/model/PostUserRes.java @@ -9,4 +9,5 @@ @AllArgsConstructor public class PostUserRes { private int userIdx; + private String jwt; } diff --git a/src/main/java/umc/crud/utils/AES128.java b/src/main/java/umc/crud/utils/AES128.java new file mode 100644 index 0000000..0df25c3 --- /dev/null +++ b/src/main/java/umc/crud/utils/AES128.java @@ -0,0 +1,45 @@ +package umc.crud.utils; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +import static java.nio.charset.StandardCharsets.UTF_8; + +// 고급 암호화 표준 +// 참고) https://sunghs.tistory.com/119 +public class AES128 { + private final String ips; + private final Key keySpec; + + public AES128(String key) { + byte[] keyBytes = new byte[16]; + byte[] b = key.getBytes(UTF_8); + System.arraycopy(b, 0, keyBytes, 0, keyBytes.length); + SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); + this.ips = key.substring(0, 16); + this.keySpec = keySpec; + } + //암호화 관련 함수 + public String encrypt(String value) throws NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, InvalidKeyException { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(ips.getBytes())); + byte[] encrypted = cipher.doFinal(value.getBytes(UTF_8)); + return new String(Base64.getEncoder().encode(encrypted)); + } + //복호화 관련함수 + public String decrypt(String value) throws NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException, InvalidKeyException { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(ips.getBytes(UTF_8))); + byte[] decrypted = Base64.getDecoder().decode(value.getBytes()); + return new String(cipher.doFinal(decrypted), UTF_8); + } +} \ No newline at end of file diff --git a/src/main/java/umc/crud/utils/JwtService.java b/src/main/java/umc/crud/utils/JwtService.java new file mode 100644 index 0000000..28e00a7 --- /dev/null +++ b/src/main/java/umc/crud/utils/JwtService.java @@ -0,0 +1,71 @@ +package umc.crud.utils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.stereotype.Service; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import umc.crud.config.BaseException; +import umc.crud.config.secret.Secret; + +import javax.servlet.http.HttpServletRequest; +import java.util.Date; + +import static umc.crud.config.BaseResponseStatus.*; + +@Service +public class JwtService { + + /** + * JWT 생성 + * @param userIdx + * @return String + */ + public String createJwt(int userIdx){ + Date now = new Date(); + return Jwts.builder() + .setHeaderParam("type","jwt") // type=jwt로 설정 + .claim("userIdx",userIdx) // payload에 담길 데이터(userIdx로 전달받은 값) + .setIssuedAt(now) // 발급 시간 + .setExpiration(new Date(System.currentTimeMillis()+1*(1000*60*60*24*365))) // 해당 JWT 만료 시간(1년 뒤로 설정) + .signWith(SignatureAlgorithm.HS256, Secret.JWT_SECRET_KEY) // 서명 알고리즘(HS256), 비밀키 + .compact(); // 토큰 생성 + } + + /** + * Header에서 X-ACCESS-TOKEN으로 JWT 값 추출 + * @return String + */ + public String getJwt(){ + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + return request.getHeader("X-ACCESS-TOKEN"); // 프론트에서 "X-ACCESS-TOKEN"라는 이름의 header에 담아 넘겨주었다고 가정, Postman 돌릴 때 header에 값 넣어주기 + } + + /** + * JWT에서 userIdx 추출 + * @return int + * @throws BaseException + */ + public int getUserIdx() throws BaseException { + //1. 헤더에서 JWT 추출 + String accessToken = getJwt(); + if(accessToken == null || accessToken.length() == 0){ + throw new BaseException(EMPTY_JWT); + } + + // 2. JWT parsing + Jws claims; + try{ + claims = Jwts.parser() + .setSigningKey(Secret.JWT_SECRET_KEY) + .parseClaimsJws(accessToken); + }catch (Exception ignored) { + throw new BaseException(INVALID_JWT); + } + + // 3. userIdx 추출 + return claims.getBody().get("userIdx",Integer.class); // jwt에서 userIdx 추출합 + } +} \ No newline at end of file