[Spring] 스프링부트 인증 - AOP 활용

류재성's avatar
Apr 11, 2024
[Spring] 스프링부트 인증 - AOP 활용
 

1. AOP란?

 
💡
AOP (Aspect-Oriented Programming)는 프로그래밍 패러다임의 하나로, 관심사를 분리하여 코드의 모듈화를 개선하기 위한 기법이다. 이를 통해 코드의 가독성과 유지 보수성을 높일 수 있다.
 

2. AOP 용어 정리

 
💡
1. Aspect 생성 - 로깅 기능(Advice)을 포함하는 Aspect를 정의 (클래스 생성) 2. Advice 정의 - 메서드 실행 전후에 로그를 남기는 코드를 작성 (수행할 메서드 생성) 3. Pointcut 설정 - 로깅 기능을 적용할 메서드를 결정. 예를 들어, 모든 public 메서드에 로깅을 적용하려면 해당하는 패턴을 Pointcut에 지정한다. (깃발에 별칭주기) 4. Advice 적용: - 설정한 Pointcut에 따라 메서드 실행 전후에 로그를 남기는 Advice가 실행됩니다. (PointCut(별칭) 적용)
 
 

3. 핸들러 만들기

package shop.mtcoding.blog._core.errors; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect //AOP 등록 @Component //IoC 등록 public class MyValidationHandler { // Advice ( 부가로직 메서드) //Advice가 수행될 위치 == PointCut @Before("@annotation(org.springframework.web.bind.annotation.GetMapping)") //get매핑일때만 실행 public void hello(JoinPoint jp){ //, JoinPoint.aspect.lang System.out.println("MyValidationHandler : hello_________________________"); } }
 
notion image
 
 
notion image
 
💡
@Before 어노테이션을 사용하면 GetMapping이 실행되기 전 메서드가 호출된다. @After 어노테이션을 사용하면 GetMapping이 실행된 후 메서드가 호출된ㄷ
 
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect //AOP 등록 @Component //IoC 등록 public class MyValidationHandler { // Advice ( 부가로직 메서드) //Advice가 수행될 위치 == PointCut @Before("@annotation(org.springframework.web.bind.annotation.GetMapping)") //get매핑일때만 실행 public void hello(JoinPoint jp){ //, JoinPoint.aspect.lang Object[] args = jp.getArgs(); //파라미터(매개변수) System.out.println("크기 : " +args.length); System.out.println("MyValidationHandler : hello_________________________"); } }
notion image
 
notion image
 
💡
@PathVariable Integer id 로 표기되어 있어도 파라미터를 분석해서 하나의 데이터로 처리한다.
 
 

4. AOP 를 활용해 유효성 검사하기

 
💡
유효성 검사는 BODY 데이터가 있는 PostMapping 과 PutMapping 에 사용한다.
 
postman으로 로그인
notion image
 
 
@PostMapping("/api/boards") public ResponseEntity<?> save(@Valid @RequestBody BoardRequest.SaveDTO reqDTO, Errors errors) { if(errors.hasErrors()){ for (FieldError error : errors.getFieldErrors()){ System.out.println(error.getField()); System.out.println(error.getDefaultMessage()); } }
 
 
💡
컨트롤러의 파라미터 앞에 @Valid , 뒤에는 Errors errors 를 붙인다. @Valid 는 파라미터의 필드에 유효성 검사를 도와준다 (NotNull, Min, Max 등). errors 에는 유효성 검사에서 실패한 필드와 실패 이유를 저장한다.
 
UserRequest.SaveDTO
notion image
 
package shop.mtcoding.blog._core.errors; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect //AOP 등록 @Component //IoC 등록 public class MyValidationHandler { // Advice ( 부가로직 메서드) //Advice가 수행될 위치 == PointCut @Before("@annotation(org.springframework.web.bind.annotation.PostMapping)") //post매핑일때만 실행 public void hello(JoinPoint jp){ //, JoinPoint.aspect.lang Object[] args = jp.getArgs(); //파라미터(매개변수) System.out.println("크기 : " +args.length); for(Object arg : args){ System.out.println("매개변수 : " + arg); } System.out.println("MyValidationHandler : hello_________________________"); } }
 
notion image
 
notion image
 
@PostMapping("/api/boards") public ResponseEntity<?> save(@Valid @RequestBody BoardRequest.SaveDTO reqDTO, Errors errors) { if(errors.hasErrors()){ for (FieldError error : errors.getFieldErrors()){ throw new Exception400(error.getDefaultMessage()+" : "+error.getField()); } } User sessionUser = (User) session.getAttribute("sessionUser"); BoardResponse.DTO respDTO = boardService.글쓰기(reqDTO, sessionUser); return ResponseEntity.ok(new ApiUtil(respDTO)); }
 
오류가 발생하면 throw 를 날려서 메세지가 하나씩 발생하도록 한다.
 
notion image
 
 

5. 코드 모듈화

 
package shop.mtcoding.blog._core.errors; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; import org.springframework.validation.Errors; import org.springframework.validation.FieldError; import shop.mtcoding.blog._core.errors.exception.Exception400; @Aspect //AOP 등록 @Component //IoC 등록 public class MyValidationHandler { // Advice ( 부가로직 메서드) //Advice가 수행될 위치 == PointCut @Before("@annotation(org.springframework.web.bind.annotation.PostMapping) || @annotation(org.springframework.web.bind.annotation.PutMapping)") //get매핑일때만 실행 public void hello(JoinPoint jp) { // JoinPoint.aspect.lang Object[] args = jp.getArgs(); //파라미터(매개변수) for (Object arg : args) { if (arg instanceof Errors) { Errors errors = (Errors) arg; if (errors.hasErrors()) { for (FieldError error : errors.getFieldErrors()) { throw new Exception400(error.getDefaultMessage() + " : " + error.getField()); } } } } System.out.println("MyValidationHandler : hello_________________________"); } }
 
💡
받은 데이터를 핸들러에 포함시킨다. AOP는 바디데이터가 있는 Post 와 Put 요청에서만 사용했다. PostMapping와 PutMapping 이 들어오면 핸들러가 실행된다.
 

6. 표현식

 
com.example.service 패키지의 MyService에서 save 를 호출할 때 수행된다.
@Pointcut("execution(* com.example.service.MyService.save(..))") public void saveMethod() {}
 
com.example.controller 패키지 내의 UserController 클래스의 모든 메서드가 호출될때 수행된다.
@Pointcut("execution(* com.example.controller.UserController.*(..))") public void userControllerMethods() {}
 
리턴타입 지정
표현식
설명
*
모든 리턴타입 허용
void
리턴타입이 void인 메서드 선택
!void
리턴타입이 void가 아닌 메서드 선택
패키지 지정
표현식
설명
co.kr.mono.aop.entity
co.kr.mono.aop.entity 패키지만 선택
co.kr.mono.aop.entity..
co.kr.mono.aop.entity 패키지로 시작하는 모든 패키지 선택
co.kr.mono.aop.entity..impl
co.kr.mono.aop.entity.. 패키지로 시작하고 마지막 패키지 이름이 impl 로 끝나는 패키지 선택
 
메서드 지정
표현식
설명
*(..)
모든 메서드 선택
print*(..)
메서드명이 print로 시작하는 모든 메서드 선택
 
매개변수 지정
표현식
설명
(..)
모든 매개변수
(*)
반드시 1개의 매개변수를 가지는 메서드만 선택
(com.devlih.domain.user.model.User)
매개변수로 User를 가지는 메서드만 선택. 반드시 풀패키지명을 적어야 한다.
(!com.devlih.domain.user.model.User)
매개변수로 User를 가지지 않는 메서드만 선택
(Integer,..)
1개 이상의 매개변수를 가지되, 첫번째 매개변수의 타입이 Integer인 메서드만 선택
(Integer,*)
반드시 2개의 매개변수를 가지되, 첫번째 매개변수의 타입이 Integer인 메서드만 선택
 
 
execution(* onj.spring.aop.*.*(..)) : onj.spring.aop 패키지의 모든 메소드가 포인트 컷 execution(* onj.spring.aop..*.*(..)) : onj.spring.aop 패키지와 하위 패키지의 모든 메소드가 포인트 컷 execution(public void insert*(..)) : public에 리턴값, 패키지명 없고 메서드 이름은 insert로 시작, 인자값은 0개 이상인 메서드가 포인트 컷 execution(public * *(..)) : public 메소드가 포인트 컷 execution(* onj.spring.aop.*.*()) : 리턴형 관계없고 onj.spring.aop 패키지의 모든 클래스, 인자값이 없는 모든 메서드가 포인트 컷 execution(* onj.spring.aop..*.*(..)) : 리턴형 관계없고 onj.spring.aop 패키지 및 하위 패키지에 있는 모든 클래스, 인자값이 0개 이상인 메서드가 포인트 컷 execution(* delete*(*)) : 메서드 이름이 delete으로 시작하는 인자값이 1개인 메서드가 포인트 컷 execution(* delete*(*,*)) : 메서드 이름이 delete로 시작하는 인자값이 2개인 메서드가 포인트 컷
 
💡
특정 어노테이션의 파라미터로 있는 메소드를 pointcut으로 잡으려면, execution expression을 이용하여 어노테이션이 적용된 메소드를 대상으로 pointcut을 작성하면 됩니다. 다만, 이 경우에는 @annotation 대신에 @args 키워드를 사용해야 합니다.
 

6. 인증 코드 정리

 
public class RegexTest { @Test public void 한글만된다_test() throws Exception { String value = "한글"; boolean result = Pattern.matches("^[가-힣]+$", value); System.out.println("테스트 : " + result); } @Test public void 한글은안된다_test() throws Exception { String value = "abc"; boolean result = Pattern.matches("^[^ㄱ-ㅎ가-힣]*$", value); System.out.println("테스트 : " + result); } @Test public void 영어만된다_test() throws Exception { String value = "ssar"; boolean result = Pattern.matches("^[a-zA-Z]+$", value); System.out.println("테스트 : " + result); } @Test public void 영어는안된다_test() throws Exception { String value = "가22"; boolean result = Pattern.matches("^[^a-zA-Z]*$", value); System.out.println("테스트 : " + result); } @Test public void 영어와숫자만된다_test() throws Exception { String value = "ab12"; boolean result = Pattern.matches("^[a-zA-Z0-9]+$", value); System.out.println("테스트 : " + result); } @Test public void 영어만되고_길이는최소2최대4이다_test() throws Exception { String value = "ssar"; boolean result = Pattern.matches("^[a-zA-Z]{2,4}$", value); System.out.println("테스트 : " + result); } // username, email, fullname @Test public void user_username_test() throws Exception { String username = "ssar"; boolean result = Pattern.matches("^[a-zA-Z0-9]{2,20}$", username); System.out.println("테스트 : " + result); } @Test public void user_fullname_test() throws Exception { String fullname = "메타코딩"; boolean result = Pattern.matches("^[a-zA-Z가-힣]{1,20}$", fullname); System.out.println("테스트 : " + result); } @Test public void user_email_test() throws Exception { String fullname = "ssaraa@nate.com"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^[a-zA-Z0-9]{2,10}@[a-zA-Z0-9]{2,6}\\.[a-zA-Z]{2,3}$", fullname); System.out.println("테스트 : " + result); } @Test public void account_gubun_test1() throws Exception { String gubun = "DEPOSIT"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^(DEPOSIT)$", gubun); System.out.println("테스트 : " + result); } @Test public void account_gubun_test2() throws Exception { String gubun = "TRANSFER"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^(DEPOSIT|TRANSFER)$", gubun); System.out.println("테스트 : " + result); } @Test public void account_tel_test1() throws Exception { String tel = "010-3333-7777"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^[0-9]{3}-[0-9]{4}-[0-9]{4}", tel); System.out.println("테스트 : " + result); } @Test public void account_tel_test2() throws Exception { String tel = "01033337777"; // ac.kr co.kr or.kr boolean result = Pattern.matches("^[0-9]{11}", tel); System.out.println("테스트 : " + result); } }
Share article

{CODE-RYU};