feat: AES-256-GCM 암호화 적용 (tel, email), 로그/배포 설정 추가
This commit is contained in:
@@ -2,6 +2,7 @@ package com.owrawww.service;
|
|||||||
|
|
||||||
import com.owrawww.domain.Careers;
|
import com.owrawww.domain.Careers;
|
||||||
import com.owrawww.domain.mapper.CareersMapper;
|
import com.owrawww.domain.mapper.CareersMapper;
|
||||||
|
import com.owrawww.util.AesUtil;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -22,6 +23,7 @@ public class CareersService {
|
|||||||
private String uploadPath;
|
private String uploadPath;
|
||||||
|
|
||||||
private final CareersMapper careersMapper;
|
private final CareersMapper careersMapper;
|
||||||
|
private final AesUtil aesUtil;
|
||||||
|
|
||||||
public boolean submit(Careers careers, MultipartFile file) throws IOException {
|
public boolean submit(Careers careers, MultipartFile file) throws IOException {
|
||||||
Careers saved = new Careers();
|
Careers saved = new Careers();
|
||||||
@@ -29,8 +31,8 @@ public class CareersService {
|
|||||||
saved.setTitle(careers.getTitle());
|
saved.setTitle(careers.getTitle());
|
||||||
saved.setComment(careers.getContent());
|
saved.setComment(careers.getContent());
|
||||||
saved.setName(careers.getName());
|
saved.setName(careers.getName());
|
||||||
saved.setTel(careers.getTel());
|
saved.setTel(aesUtil.encrypt(careers.getTel()));
|
||||||
saved.setEmail(careers.getEmail());
|
saved.setEmail(aesUtil.encrypt(careers.getEmail()));
|
||||||
saved.setTopCode(2);
|
saved.setTopCode(2);
|
||||||
saved.setLeftCode(1);
|
saved.setLeftCode(1);
|
||||||
saved.setSubGubun(1);
|
saved.setSubGubun(1);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.owrawww.service;
|
|||||||
|
|
||||||
import com.owrawww.domain.Inquiry;
|
import com.owrawww.domain.Inquiry;
|
||||||
import com.owrawww.domain.mapper.InquiryMapper;
|
import com.owrawww.domain.mapper.InquiryMapper;
|
||||||
|
import com.owrawww.util.AesUtil;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ import org.springframework.stereotype.Service;
|
|||||||
public class InquiryService {
|
public class InquiryService {
|
||||||
|
|
||||||
private final InquiryMapper inquiryMapper;
|
private final InquiryMapper inquiryMapper;
|
||||||
|
private final AesUtil aesUtil;
|
||||||
|
|
||||||
public boolean submit(Inquiry inquiry) {
|
public boolean submit(Inquiry inquiry) {
|
||||||
Inquiry savedInquiry = new Inquiry();
|
Inquiry savedInquiry = new Inquiry();
|
||||||
@@ -17,8 +19,8 @@ public class InquiryService {
|
|||||||
savedInquiry.setTitle(inquiry.getTitle());
|
savedInquiry.setTitle(inquiry.getTitle());
|
||||||
savedInquiry.setComment(inquiry.getContent());
|
savedInquiry.setComment(inquiry.getContent());
|
||||||
savedInquiry.setName(inquiry.getName());
|
savedInquiry.setName(inquiry.getName());
|
||||||
savedInquiry.setTel(inquiry.getTel());
|
savedInquiry.setTel(aesUtil.encrypt(inquiry.getTel()));
|
||||||
savedInquiry.setEmail(inquiry.getEmail());
|
savedInquiry.setEmail(aesUtil.encrypt(inquiry.getEmail()));
|
||||||
savedInquiry.setTopCode(2);
|
savedInquiry.setTopCode(2);
|
||||||
savedInquiry.setLeftCode(1);
|
savedInquiry.setLeftCode(1);
|
||||||
savedInquiry.setSubGubun(1);
|
savedInquiry.setSubGubun(1);
|
||||||
|
|||||||
78
src/main/java/com/owrawww/util/AesUtil.java
Normal file
78
src/main/java/com/owrawww/util/AesUtil.java
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package com.owrawww.util;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.GCMParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES-256-GCM 암호화/복호화 유틸리티
|
||||||
|
* - IV(12 bytes) + 암호문을 Base64로 인코딩하여 저장
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class AesUtil {
|
||||||
|
|
||||||
|
private static final String ALGORITHM = "AES/GCM/NoPadding";
|
||||||
|
private static final int IV_LENGTH = 12; // GCM 권장 IV 크기
|
||||||
|
private static final int TAG_BIT_LEN = 128; // GCM 인증 태그 크기
|
||||||
|
|
||||||
|
private final SecretKey secretKey;
|
||||||
|
|
||||||
|
public AesUtil(@Value("${app.encryption.key}") String base64Key) {
|
||||||
|
byte[] keyBytes = Base64.getDecoder().decode(base64Key);
|
||||||
|
if (keyBytes.length != 32) {
|
||||||
|
throw new IllegalArgumentException("암호화 키는 Base64 인코딩된 32바이트(256bit)여야 합니다.");
|
||||||
|
}
|
||||||
|
this.secretKey = new SecretKeySpec(keyBytes, "AES");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 평문 → AES-256-GCM 암호화 → Base64 문자열
|
||||||
|
*/
|
||||||
|
public String encrypt(String plainText) {
|
||||||
|
if (plainText == null || plainText.isEmpty()) return plainText;
|
||||||
|
try {
|
||||||
|
byte[] iv = new byte[IV_LENGTH];
|
||||||
|
new SecureRandom().nextBytes(iv);
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(TAG_BIT_LEN, iv));
|
||||||
|
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
|
||||||
|
|
||||||
|
// IV + 암호문 결합 후 Base64 인코딩
|
||||||
|
byte[] combined = new byte[IV_LENGTH + encrypted.length];
|
||||||
|
System.arraycopy(iv, 0, combined, 0, IV_LENGTH);
|
||||||
|
System.arraycopy(encrypted, 0, combined, IV_LENGTH, encrypted.length);
|
||||||
|
|
||||||
|
return Base64.getEncoder().encodeToString(combined);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("암호화 실패", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base64 암호문 → AES-256-GCM 복호화 → 평문
|
||||||
|
*/
|
||||||
|
public String decrypt(String encryptedText) {
|
||||||
|
if (encryptedText == null || encryptedText.isEmpty()) return encryptedText;
|
||||||
|
try {
|
||||||
|
byte[] combined = Base64.getDecoder().decode(encryptedText);
|
||||||
|
byte[] iv = new byte[IV_LENGTH];
|
||||||
|
byte[] cipherText = new byte[combined.length - IV_LENGTH];
|
||||||
|
|
||||||
|
System.arraycopy(combined, 0, iv, 0, IV_LENGTH);
|
||||||
|
System.arraycopy(combined, IV_LENGTH, cipherText, 0, cipherText.length);
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(TAG_BIT_LEN, iv));
|
||||||
|
return new String(cipher.doFinal(cipherText), "UTF-8");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("복호화 실패", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
{"properties": [{
|
{"properties": [
|
||||||
"name": "app.upload.path",
|
{
|
||||||
"type": "java.lang.String",
|
"name": "app.upload.path",
|
||||||
"description": "A description for 'app.upload.path'"
|
"type": "java.lang.String",
|
||||||
}]}
|
"description": "A description for 'app.upload.path'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "app.encryption.key",
|
||||||
|
"type": "java.lang.String",
|
||||||
|
"description": "A description for 'app.encryption.key'"
|
||||||
|
}
|
||||||
|
]}
|
||||||
@@ -28,6 +28,9 @@ logging:
|
|||||||
max-history: 30
|
max-history: 30
|
||||||
file-name-pattern: /home/www/owrainfo/logs/owrawww.%d{yyyy-MM-dd}.%i.log
|
file-name-pattern: /home/www/owrainfo/logs/owrawww.%d{yyyy-MM-dd}.%i.log
|
||||||
|
|
||||||
app:
|
app:
|
||||||
upload:
|
upload:
|
||||||
path: /home/www/owrainfo/uploads
|
path: /home/www/owrainfo/uploads
|
||||||
|
encryption:
|
||||||
|
key: PIYAsB81yr7uETVoM/E7Sz9Sp5kNB46uHyjnfQE3XOs= # 운영 전용 키로 교체 필요 (base64 인코딩된 32바이트)
|
||||||
|
|
||||||
@@ -30,3 +30,5 @@ server:
|
|||||||
app:
|
app:
|
||||||
upload:
|
upload:
|
||||||
path: D:/uploads/owrawww/careers # 기본값 (프로파일별로 override)
|
path: D:/uploads/owrawww/careers # 기본값 (프로파일별로 override)
|
||||||
|
encryption:
|
||||||
|
key: PIYAsB81yr7uETVoM/E7Sz9Sp5kNB46uHyjnfQE3XOs= # AES-256 키 (운영에서는 반드시 별도 키로 교체)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<div class="inner">
|
<div class="inner">
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<ul class="ft_menu">
|
<ul class="ft_menu">
|
||||||
<li><a href="">이용약관</a></li>
|
<li><a href="/terms">이용약관</a></li>
|
||||||
<li><a href="" class="point">개인정보처리방침</a></li>
|
<li><a href="/privacy" class="point">개인정보처리방침</a></li>
|
||||||
<li><a href="">회사소개</a></li>
|
<li><a href="/company/intro">회사소개</a></li>
|
||||||
<li><a href="">제휴문의</a></li>
|
<li><a href="/bbs/partnership">제휴문의</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="ft-info">
|
<div class="ft-info">
|
||||||
<div class="company-info">
|
<div class="company-info">
|
||||||
|
|||||||
Reference in New Issue
Block a user