2024.11.17 - [BE/๐ Spring] - [Spring Boot] AOP ๊ฐ๋ ์ ๋ฆฌ
2024.11.18 - [BE/๐ Spring] - [Spring Boot] AOP ์ฌ์ฉ ๋ฐฉ๋ฒ๊ณผ ์์์ฝ๋
ํ์ฌ ์งํํ๊ณ ์๋ ํ๋ก์ ํธ์ ์ผ๋ถ API์์ ์์ฒญ/์๋ต ๋ฐ์ดํฐ๋ฅผ End-to-End ์ํธํ ํด์ ํต์ ํ๊ณ ์์ต๋๋ค. ๊ทธ๋ฌ๋ค ๋ณด๋ Controller ํน์ ์๋ณตํธํ์ ๊ด๋ จ ์๋ ๋ก์ง์ ์ํธํ ๊ด๋ จ ์ฝ๋๊ฐ ๋ค์ด๊ฐ์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ์์์ต๋๋ค.
์๋ณตํธํ๋ฅผ ์ํํ๋ static object๋ฅผ ๋ง๋ค์ด ์ฌ์ฉ์ ํด์์๋๋ฐ, AOP๋ฅผ ์ด์ฉํด์ ์ด๋ฅผ ๊ฐ์ ํด ๋ณด๋ฉด ์ด๋จ๊น ์ถ์์ต๋๋ค. ์์ฒญ ๊ฐ์ฒด ์์ฒด๋ฅผ ๊ฐ๋ก์ฑ ๋ณตํธํ ํด์ ํ๋ฌธ๊ฐ์ ๋ฐ๋ก Controller์์ ์ฌ์ฉํ ์ ์๊ฒ ํ๊ณ , ์๋ต์ ํด๋ผ์ด์ธํธ์ ์ ๋ฌํ๊ธฐ ์ ์ ์๋ต ๊ฐ์ ๊ฐ๋ก์ฑ ์ํธํ ํ๊ณ ์ ๋ฌํ ์ ์๊ฒ ํ์ฌ Controller์์ ํก๋จ ๊ด์ฌ์ฌ์ธ ์๋ณตํธํ aspect๋ฅผ ๋ถ๋ฆฌํด ๋ผ ์ ์์ ๊ฒ์ด๋ผ ์๊ฐํ์ต๋๋ค.
์ผ๋ถ API์์ ์๋ณตํธํ๊ฐ ์งํ๋ฌ๊ธฐ ๋๋ฌธ์ Controller ํด๋์ค๋ ํจํค์ง ๋จ์๊ฐ ์๋ ์ปค์คํ ์ ๋ํ ์ด์ ์๋ ๋ฉ์๋๋ฅผ ๋์์ผ๋ก ์์ฒญ/์๋ต ์๋ณตํธํ๋ฅผ ์งํํ๊ธฐ๋ก ํ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ ๋ํ ์ด์ ์ ์ฌ์ฉํ๋ฉด ์ด๋ก ์ธํด ์ด ๋ฉ์๋๊ฐ ์ด๋ค ๋์์ ํ ๊ฒ์ธ์ง ํจ๊ป ์ผํ๋ ๊ฐ๋ฐ์์๊ฒ ๋ช ์์ ์ผ๋ก ๋ณด์ฌ์ค ์ ์์ ๊ฒ์ด๋ผ ํ๋จํ์ต๋๋ค.
๊ฒฐ๋ก ์ ์ผ๋ก ํด๋น ๋ฐฉ๋ฒ์ ์ค์ ์ฝ๋์ ์ ์ฉํ์ง ๋ชปํ์ง๋ง AOP์ ์ ๋ํ ์ด์ ์กฐํฉ์ ๊ณต์ ํ๋ฉด ์ข์ ๊ฒ ๊ฐ์ ๊ธ์ ์์ฑํ์ต๋๋ค
๊ธฐ๋ณธ์ฝ๋
@PostMapping("/regist")
public ResponseEntity<BaseResponse> regist(
@RequestBody RegistAccountRequest request
) {
RegistAccountResponse registAccountResponse = bankService.registAccount(request);
BaseResponse response = BaseResponse.builder()
.code("00000")
.msg("success")
.data(registAccountResponse)
.build();
return ResponseEntity.ok(response);
}
// request
{
"owner_name" : "user1",
"password" : "2017",
"amount" : 1000000
}
// response
{
"code": "00000",
"msg": "success",
"data": {
"accountNumber": "084256-19-1458"
}
}
๊ธฐ๋ณธ ์ฝ๋๋ ํ๋ฌธ request/response๋ฅผ ์ฌ์ฉํฉ๋๋ค.
์๋ณตํธํ
@PostMapping("/regist")
public ResponseEntity<BaseResponse> regist(
@RequestBody BaseRequest request
) throws Exception {
String plaintText = AESUtil.decrypt(request.getData()); // v
RegistAccountRequest registAccountRequest = objectMapper.readValue(plaintText, RegistAccountRequest.class); // v
RegistAccountResponse registAccountResponse = bankService.registAccount(registAccountRequest);
String dataJson = objectMapper.writeValueAsString(registAccountResponse); // v
BaseResponse response = BaseResponse.builder()
.code("00000")
.msg("success")
.data(AESUtil.encrypt(dataJson)) // v
.build();
return ResponseEntity.ok(response);
}
์๊ฒฉํ ๋ณด์์ ์ํด End-to-End ์ํธํ๋ฅผ ์งํํ๋ค๊ณ ํด๋ด ์๋ค. ์์ ์ฝ๋์์๋ Controller์ request๋ฅผ ๋ณตํธํ ํ๊ณ response๋ฅผ ์ํธํ ํ๋ ์ฝ๋๋ฅผ ๊ฐ๋จํ ์์ฑํ์ต๋๋ค. ์ด ์ค์์ reqeust๋ฅผ ํ๋ฌธ์ผ๋ก ๋ง๋๋ ๊ฒ๊ณผ, ์๋ต์ body์ ๋ค์ด๊ฐ๋ ๊ฐ์ฒด๋ฅผ ์ํธํ ํ๋ ๊ฒ์ ์ปค์คํ ์ ๋ํ ์ด์ ๊ณผ aop๋ฅผ ์ฌ์ฉํด์ ์์ฑํด ๋ณด๊ฒ ์ต๋๋ค.
// request
{
"data" : "/PJ/p778x9sf8G0YXUUxGwn5NRhrPu4gWqwxKxDdQMxwq+wxJAxOucVvZSmzEnjNu/Z41THSQKzaQn8IVEZxfg=="
}
// response
{
"code": "00000",
"msg": "success",
"data": "dMDQDB5ePZTBfxDJtkSREbZEHxguTT5RI+4MoBjtcVNWimRi/Ypg1NlXrHr9Rn+O"
}
์๋ณตํธํ์ request/response ์์๋ ์ ์ฝ๋์ ๊ฐ์ต๋๋ค. ์์๋ฅผ ์ํด ๊ฐ๋จํ ์ํธํ๋ฅผ ์งํํ์ต๋๋ค.
AESUtil
public class AESUtil {
private static final String SECRET_KEY = "youabledevsecret";
public static String encrypt(String data) throws Exception {
SecretKey secretKey = new SecretKeySpec(SECRET_KEY.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
return Base64.getEncoder().encodeToString(cipher.doFinal(data.getBytes()));
}
public static String decrypt(String data) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(SECRET_KEY.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
return new String(cipher.doFinal(Base64.getDecoder().decode(data)));
}
}
์์ ์ฝ๋๋ฅผ ์ํด ๋ณด์์ ์ทจ์ฝํ ์๋ณตํธํ Util์ ์์ฑํ์ต๋๋ค.
Custom Annotation
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DecryptRequest {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptResponse {
}
์ปค์คํ ์ ๋ํ ์ด์ ์ ์์ฑํด ์ค๋๋ค.
DecryptRequest๋ ์ํธํ๋ ์์ฒญ ๋ฐ์ดํฐ๋ฅผ ๋ณตํธํ ํ๊ณ , EncryptResponse๋ ์๋ต์ ์ํธํ ํ๋๋ฐ ์ฌ์ฉํฉ๋๋ค
AOP
@Aspect
@Component
@RequiredArgsConstructor
public class EncryptionAspect {
}
Aspect๋ฅผ ์ถ๊ฐํด ์ค๋๋ค.
@Around("@annotation(DecryptRequest)")
public Object decryptRequest(ProceedingJoinPoint joinPoint, DecryptRequest decryptRequest) throws Throwable {
Object[] args = joinPoint.getArgs();
if (args.length > 0 && args[0] instanceof BaseRequest request) {
String decryptedJson = AESUtil.decrypt(request.getData());
request.setData(decryptedJson);
}
return joinPoint.proceed(args);
}
DecryptRequest ์ ๋ํ ์ด์ ์ด ๋ถ์ด ์๋ ๋ฉ์๋์ ์ฒซ๋ฒ์งธ ํ๋ผ๋ฏธํฐ์ธ BaseRequest์ data๋ฅผ ๋ณตํธํ ํ์ฌ ๋ค์ ํด๋น ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌํด์ค๋๋ค.
@Around("@annotation(EncryptResponse)")
public Object encryptResponse(ProceedingJoinPoint joinPoint, EncryptResponse encryptResponse) throws Throwable {
Object result = joinPoint.proceed();
if (result instanceof ResponseEntity<?> responseEntity) {
Object body = responseEntity.getBody();
if (body instanceof BaseResponse baseResponse) {
String dataJson = objectMapper.writeValueAsString(baseResponse.getData());
String encryptedData = AESUtil.encrypt(dataJson);
BaseResponse encryptedResponse = BaseResponse.builder()
.code(baseResponse.getCode())
.msg(baseResponse.getMsg())
.data(encryptedData)
.build();
return ResponseEntity.ok(encryptedResponse);
}
}
return result;
}
EncryptResponse ์ ๋ํ ์ด์ ์ด ์ถ๊ฐ๋ ๋ฉ์๋๊ฐ ํธ์ถ๋๊ณ ๋ ํ ๋ฆฌํด ๊ฐ์ธ ResponseEntity<BaseResponse>์ ์ ๋ฌ ๋ฐ์ BaseResponse์ data ๋ถ๋ถ์ ์ํธํ ํ์ฌ ๋ค์ ํธ์ถ๋ ๋ฉ์๋๋ก ์ ๋ฌํฉ๋๋ค.
Result
@DecryptRequest
@EncryptResponse
@PostMapping("/regist")
public ResponseEntity<BaseResponse> regist(
@RequestBody BaseRequest request
) throws Exception {
RegistAccountRequest registAccountRequest = objectMapper.readValue(request.getData(), RegistAccountRequest.class); // v
RegistAccountResponse registAccountResponse = bankService.registAccount(registAccountRequest);
BaseResponse response = BaseResponse.builder()
.code("00000")
.msg("success")
.data(registAccountResponse)
.build();
return ResponseEntity.ok(response);
}
์์ฒญ/์๋ต ์๋ณตํธํ๋ฅผ ์ํ๋ ๋ฉ์๋์ ์ ๋ํ ์ด์ ์ ์ถ๊ฐํฉ๋๋ค. Controller ๋ก์ง์์ ์๋ณตํธํ ํ๋ ๋ก์ง์ด Aspect๋ก ๋ถ๋ฆฌ๋์์ต๋๋ค
// request
{
"data" : "/PJ/p778x9sf8G0YXUUxGwn5NRhrPu4gWqwxKxDdQMxwq+wxJAxOucVvZSmzEnjNu/Z41THSQKzaQn8IVEZxfg=="
}
// response
{
"code": "00000",
"msg": "success",
"data": "dMDQDB5ePZTBfxDJtkSREfohsFHbBF6VrddM4ZnyN3hWimRi/Ypg1NlXrHr9Rn+O"
}
'BE > ๐ Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring Boot] AOP ์ฌ์ฉ ๋ฐฉ๋ฒ๊ณผ ์์์ฝ๋ (0) | 2024.11.18 |
---|---|
[Spring Boot] AOP ๊ฐ๋ ์ ๋ฆฌ (0) | 2024.11.17 |
[MQTT] Spring Boot ์์ MQTT(mosquitto) ์ฌ์ฉํ๊ธฐ (4) | 2024.11.13 |
[Spring Boot] Custom Validation annotation (0) | 2024.11.10 |
[Spring Boot] RestControllerAdvice๋ก Validation Exception ํธ๋ค๋งํ๊ธฐ (1) | 2024.11.09 |
๋๊ธ