Skip to content

Commit 399aa73

Browse files
authored
Merge pull request #562 from Moadong/feature/encrypt-applicants-answer
2 parents 3523204 + cf7ecc6 commit 399aa73

File tree

5 files changed

+103
-13
lines changed

5 files changed

+103
-13
lines changed
Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,43 @@
11
package moadong.club.payload.dto;
22

33
import lombok.Builder;
4+
import lombok.extern.slf4j.Slf4j;
45
import moadong.club.entity.ClubApplication;
56
import moadong.club.entity.ClubQuestionAnswer;
67
import moadong.club.enums.ApplicationStatus;
8+
import moadong.global.exception.ErrorCode;
9+
import moadong.global.exception.RestApiException;
10+
import moadong.global.util.AESCipher;
711

12+
import java.util.ArrayList;
813
import java.util.List;
914

1015
@Builder
16+
@Slf4j
1117
public record ClubApplicantsResult(
1218
String questionId,
1319
ApplicationStatus status,
1420
List<ClubQuestionAnswer> answers
1521
) {
16-
public static ClubApplicantsResult of(ClubApplication application) {
22+
public static ClubApplicantsResult of(ClubApplication application, AESCipher cipher) {
23+
List<ClubQuestionAnswer> decryptedAnswers = new ArrayList<>();
24+
try {
25+
for (ClubQuestionAnswer answer : application.getAnswers()) {
26+
String decryptedValue = cipher.decrypt(answer.getValue());
27+
decryptedAnswers.add(ClubQuestionAnswer.builder()
28+
.id(answer.getId())
29+
.value(decryptedValue)
30+
.build());
31+
}
32+
} catch (Exception e) {
33+
log.error("AES_CIPHER_ERROR", e);
34+
throw new RestApiException(ErrorCode.AES_CIPHER_ERROR);
35+
}
36+
1737
return ClubApplicantsResult.builder()
1838
.questionId(application.getQuestionId())
1939
.status(application.getStatus())
20-
.answers(application.getAnswers())
40+
.answers(decryptedAnswers)
2141
.build();
2242
}
23-
}
43+
}

backend/src/main/java/moadong/club/service/ClubApplyService.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package moadong.club.service;
22

33
import lombok.AllArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
45
import moadong.club.entity.*;
56
import moadong.club.enums.ClubApplicationQuestionType;
6-
import moadong.club.enums.ApplicationStatus;
77
import moadong.club.payload.dto.ClubApplicantsResult;
88
import moadong.club.payload.request.ClubApplicationCreateRequest;
99
import moadong.club.payload.request.ClubApplicationEditRequest;
@@ -16,6 +16,7 @@
1616
import moadong.global.exception.ErrorCode;
1717
import moadong.global.exception.RestApiException;
1818
import moadong.global.payload.Response;
19+
import moadong.global.util.AESCipher;
1920
import moadong.user.payload.CustomUserDetails;
2021
import org.springframework.http.ResponseEntity;
2122
import org.springframework.stereotype.Service;
@@ -26,11 +27,12 @@
2627

2728
@Service
2829
@AllArgsConstructor
30+
@Slf4j
2931
public class ClubApplyService {
30-
3132
private final ClubRepository clubRepository;
3233
private final ClubQuestionRepository clubQuestionRepository;
3334
private final ClubApplicationRepository clubApplicationRepository;
35+
private final AESCipher cipher;
3436

3537
public void createClubApplication(String clubId, CustomUserDetails user, ClubApplicationCreateRequest request) {
3638
ClubQuestion clubQuestion = getClubQuestion(clubId, user);
@@ -64,12 +66,20 @@ public void applyToClub(String clubId, ClubApplyRequest request) {
6466

6567
validateAnswers(request.questions(), clubQuestion);
6668

67-
List<ClubQuestionAnswer> answers = request.questions()
68-
.stream().map(answer -> ClubQuestionAnswer.builder()
69+
List<ClubQuestionAnswer> answers = new ArrayList<>();
70+
71+
try {
72+
for (ClubApplyRequest.Answer answer : request.questions()) {
73+
String encryptedValue = cipher.encrypt(answer.value());
74+
answers.add(ClubQuestionAnswer.builder()
6975
.id(answer.id())
70-
.value(answer.value())
71-
.build()
72-
).toList();
76+
.value(encryptedValue)
77+
.build());
78+
}
79+
} catch (Exception e) {
80+
log.error("AES_CIPHER_ERROR", e);
81+
throw new RestApiException(ErrorCode.AES_CIPHER_ERROR);
82+
}
7383

7484
ClubApplication application = ClubApplication.builder()
7585
.questionId(clubQuestion.getClubId())
@@ -95,7 +105,7 @@ public ClubApplyInfoResponse getClubApplyInfo(String clubId, CustomUserDetails u
95105
int accepted = 0;
96106

97107
for (ClubApplication app : submittedApplications) {
98-
applications.add(ClubApplicantsResult.of(app));
108+
applications.add(ClubApplicantsResult.of(app, cipher));
99109

100110
switch (app.getStatus()) {
101111
case SUBMITTED, SCREENING -> reviewRequired++;

backend/src/main/java/moadong/global/exception/ErrorCode.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public enum ErrorCode {
3838
LONG_EXCEED_LENGTH(HttpStatus.BAD_REQUEST, "800-3", "장문형 최대 글자를 초과하였습니다."),
3939
QUESTION_NOT_FOUND(HttpStatus.NOT_FOUND, "800-4", "존재하지 않은 질문입니다."),
4040
REQUIRED_QUESTION_MISSING(HttpStatus.BAD_REQUEST, "800-5", "필수 응답 질문이 누락되었습니다."),
41+
42+
AES_CIPHER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "900-1", "암호화 중 오류가 발생했습니다.")
4143
;
4244

4345
private final HttpStatus httpStatus;
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package moadong.global.util;
2+
3+
import org.springframework.beans.factory.annotation.Value;
4+
import org.springframework.stereotype.Component;
5+
6+
import javax.crypto.Cipher;
7+
import javax.crypto.spec.GCMParameterSpec;
8+
import javax.crypto.spec.IvParameterSpec;
9+
import javax.crypto.spec.SecretKeySpec;
10+
import java.nio.charset.StandardCharsets;
11+
import java.util.Base64;
12+
13+
@Component
14+
public class AESCipher {
15+
16+
@Value("${application.encryption.key}")
17+
private String key;
18+
19+
@Value("${application.encryption.iv}")
20+
private String iv;
21+
22+
/**
23+
* 문자열을 AES-256 알고리즘으로 암호화합니다.
24+
*
25+
* @param text 암호화할 문자열
26+
* @return Base64로 인코딩된 암호화된 문자열
27+
* @throws Exception 암호화 중 오류 발생 시
28+
*/
29+
public String encrypt(String text) throws Exception {
30+
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
31+
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
32+
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv.getBytes(StandardCharsets.UTF_8));
33+
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
34+
35+
byte[] encrypted = cipher.doFinal(text.getBytes(StandardCharsets.UTF_8));
36+
return Base64.getEncoder().encodeToString(encrypted);
37+
}
38+
39+
/**
40+
* AES-256 알고리즘으로 암호화된 문자열을 복호화합니다.
41+
*
42+
* @param cipherText Base64로 인코딩된 암호화된 문자열
43+
* @return 복호화된 원본 문자열
44+
* @throws Exception 복호화 중 오류 발생 시
45+
*/
46+
public String decrypt(String cipherText) throws Exception {
47+
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
48+
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES");
49+
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv.getBytes(StandardCharsets.UTF_8));
50+
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
51+
52+
byte[] decodedBytes = Base64.getDecoder().decode(cipherText);
53+
byte[] decrypted = cipher.doFinal(decodedBytes);
54+
return new String(decrypted, StandardCharsets.UTF_8);
55+
}
56+
}

backend/src/main/java/moadong/media/util/GoogleDriveConfig.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//package moadong.media.util;
2-
//
2+
33
//import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
44
//import com.google.api.client.json.jackson2.JacksonFactory;
55
//import com.google.api.services.drive.Drive;
@@ -12,7 +12,9 @@
1212
//import org.springframework.context.annotation.Bean;
1313
//import org.springframework.context.annotation.Configuration;
1414
//import org.springframework.util.ResourceUtils;
15-
//
15+
16+
17+
1618
//@Configuration
1719
//public class GoogleDriveConfig {
1820
//

0 commit comments

Comments
 (0)