Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/main/java/com/listywave/admin/Admin.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ public void validatePassword(String password) {
}
throw new CustomException(INVALID_ACCESS);
}

public void update(String password) {
this.password = password;
}
}
8 changes: 8 additions & 0 deletions src/main/java/com/listywave/admin/AdminController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static com.listywave.common.exception.ErrorCode.RESOURCE_NOT_FOUND;

import com.listywave.common.auth.Auth;
import com.listywave.common.exception.CustomException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
Expand All @@ -10,6 +11,7 @@
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;

@Controller
Expand Down Expand Up @@ -45,4 +47,10 @@ ResponseEntity<AdminLoginResponse> login(@RequestBody AdminLoginRequest adminLog
AdminLoginResponse result = adminService.login(adminLoginRequest.account(), adminLoginRequest.password());
return ResponseEntity.ok(result);
}

@PutMapping("/admin")
ResponseEntity<Void> updateInfo(@Auth Long adminId, @RequestBody AdminUpdateRequest request) {
adminService.update(adminId, request.password());
return ResponseEntity.ok().build();
}
}
15 changes: 14 additions & 1 deletion src/main/java/com/listywave/admin/AdminService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static com.listywave.common.exception.ErrorCode.INVALID_ACCESS;

import com.listywave.auth.application.domain.JwtManager;
import com.listywave.common.encrypt.Sha256Cipher;
import com.listywave.common.exception.CustomException;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
Expand All @@ -15,6 +16,7 @@
public class AdminService {

private final JwtManager jwtManager;
private final Sha256Cipher sha256Cipher;
private final AdminRepository adminRepository;

@Transactional(readOnly = true)
Expand All @@ -27,12 +29,23 @@ public AdminLoginResponse login(String account, String password) {
Optional<Admin> optionalAdmin = adminRepository.findByAccount(account);
if (optionalAdmin.isPresent()) {
Admin admin = optionalAdmin.get();
admin.validatePassword(password);

// 암호화 적용으로 인해, 임시로 작성해둔 코드입니다.
// 모든 어드민이 암호를 변경하면 if 조건식만 제거합니다.
if (!password.equals("12345")) {
admin.validatePassword(sha256Cipher.encrypt(password)); // 해당 라인은 제거하지 않습니다.
}
Comment on lines +32 to +37
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻


String accessToken = jwtManager.createAdminAccessToken(admin.getId());
String refreshToken = jwtManager.createAdminRefreshToken(admin.getId());
return new AdminLoginResponse(accessToken, refreshToken);
}
throw new CustomException(INVALID_ACCESS);
}

public void update(Long adminId, String password) {
Admin admin = adminRepository.getById(adminId);
String encryptedNewPassword = sha256Cipher.encrypt(password);
admin.update(encryptedNewPassword);
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/listywave/admin/AdminUpdateRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.listywave.admin;

public record AdminUpdateRequest(
String password
) {
}
82 changes: 82 additions & 0 deletions src/main/java/com/listywave/common/encrypt/Aes256Cipher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.listywave.common.encrypt;

import static com.listywave.common.exception.ErrorCode.ENCRYPT_ERROR;

import com.listywave.common.exception.CustomException;
import jakarta.annotation.PostConstruct;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Aes256Cipher {

private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그런데 이런 값을 프로필에 안넣고 노출시켜도 될까요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 애초에 클래스 이름부터 암호화 알고리즘을 나타내기 때문에, 숨길 필요가 있을까 싶긴 합니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 추가로, AES 암호화는 일반 사용자 대상으로 사용할 예정입니다!


@Value("${aes.password}")
private String password;
@Value("${aes.salt}")
private String salt;

private SecretKey secretKey;
private IvParameterSpec iv;

@PostConstruct
public void init() throws NoSuchAlgorithmException, InvalidKeySpecException {
this.secretKey = createSecretKey();
this.iv = createIv();
}

private SecretKey createSecretKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256);
return new SecretKeySpec(secretKeyFactory.generateSecret(keySpec).getEncoded(), "AES");
}

private IvParameterSpec createIv() {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return new IvParameterSpec(iv);
}

public String encrypt(String plainText) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
byte[] cipherText = cipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(cipherText);
} catch (
NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException |
InvalidAlgorithmParameterException | BadPaddingException | InvalidKeyException e) {
throw new CustomException(ENCRYPT_ERROR, e.getMessage());
}
}

public String decrypt(String cipherText) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText));
return new String(plainText);
} catch (
NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException |
InvalidAlgorithmParameterException | BadPaddingException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}
}
36 changes: 36 additions & 0 deletions src/main/java/com/listywave/common/encrypt/Sha256Cipher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.listywave.common.encrypt;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class Sha256Cipher {

@Value("${sha.salt}")
private String salt;

public String encrypt(String plainText) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
byte[] encodedHash = messageDigest.digest(plainText.concat(salt).getBytes());
return bytesToHex(encodedHash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}

private String bytesToHex(byte[] encodedHash) {
StringBuilder hexString = new StringBuilder(2 * encodedHash.length);

for (byte hash : encodedHash) {
String hex = Integer.toHexString(0xff & hash);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public enum ErrorCode {
INVALID_ACCESS_TOKEN(UNAUTHORIZED, "유효하지 않은 AccessToken 입니다. 다시 로그인해주세요."),
INVALID_ACCESS(FORBIDDEN, "접근 권한이 존재하지 않습니다."),
CANNOT_COLLECT_OWN_LIST(BAD_REQUEST, "리스트 작성자는 자신의 리스트에 콜렉트할 수 없습니다."),
ENCRYPT_ERROR(INTERNAL_SERVER_ERROR, "암호화 과정 중 문제가 발생했습니다."),

// Http Request
METHOD_ARGUMENT_TYPE_MISMATCH(BAD_REQUEST, "요청 한 값 타입이 잘못되어 binding에 실패하였습니다."),
Expand Down
8 changes: 7 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ spring:

jpa:
hibernate:
ddl-auto: create
ddl-auto: none
properties:
hibernate:
format_sql: true
Expand Down Expand Up @@ -72,3 +72,9 @@ server:
local-login:
id:
password:

aes:
password: 12345
salt: 54321
sha:
salt: 12345
6 changes: 6 additions & 0 deletions src/test/java/com/listywave/common/IntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import com.listywave.collection.application.service.CollectionService;
import com.listywave.collection.application.service.FolderService;
import com.listywave.collection.repository.CollectionRepository;
import com.listywave.common.encrypt.Aes256Cipher;
import com.listywave.common.encrypt.Sha256Cipher;
import com.listywave.history.repository.HistoryRepository;
import com.listywave.list.application.domain.list.ListEntity;
import com.listywave.list.application.service.CommentService;
Expand Down Expand Up @@ -102,6 +104,10 @@ public abstract class IntegrationTest {
protected NoticeRepository noticeRepository;
@Autowired
protected ReactionService reactionService;
@Autowired
protected Aes256Cipher aes256Cipher;
@Autowired
protected Sha256Cipher sha256Cipher;

protected User dh, js, ej, sy;
protected ListEntity list;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.listywave.common.encrypt;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

import com.listywave.common.IntegrationTest;
import org.junit.jupiter.api.Test;

class Aes256CipherTest extends IntegrationTest {

@Test
void 암호화를_한다() {
// given
String plainText = "plain";

// when
String result = aes256Cipher.encrypt(plainText);
String result2 = aes256Cipher.encrypt(plainText);
String result3 = aes256Cipher.encrypt(plainText);

// then
assertAll(
() -> assertThat(result).isEqualTo(result2).isEqualTo(result3),
() -> assertThat(plainText).isNotEqualTo(result)
.isNotEqualTo(result2)
.isNotEqualTo(result3)
);
}

@Test
void 복호화를_한다() {
// given
String plainText = "plain";

String encrypted = aes256Cipher.encrypt(plainText);

// when
String decrypted = aes256Cipher.decrypt(encrypted);

// then
assertThat(plainText).isEqualTo(decrypted);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.listywave.common.encrypt;

import static org.assertj.core.api.Assertions.assertThat;

import com.listywave.common.IntegrationTest;
import org.junit.jupiter.api.Test;

class Sha256CipherTest extends IntegrationTest {

@Test
void 암호화를_한다() {
// given
String plainText = "1234";

// when
String encrypted1 = sha256Cipher.encrypt(plainText);
String encrypted2 = sha256Cipher.encrypt(plainText);

// then
assertThat(plainText).isNotEqualTo(encrypted1);
assertThat(encrypted1).isEqualTo(encrypted2);
}
}
6 changes: 6 additions & 0 deletions src/test/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,9 @@ jwt:
local-login:
id:
password:

aes:
password: 12345
salt: 54321
sha:
salt: 12345
Loading