728x90

프로젝트 진행 중 컨트롤러의 메서드가 로그인 여부를 확인하는 부가기능을 동시에 처리하는 경우가 존재한다.

이런식으로 반복되는 로직을 AOP,Interceptor,Filter 등을 통해 반복되는 부가기능을 공통적으로 처리할 수 있다.

 

로그인 확인이 필요한 경우

프로젝트를 진행하다가 보면 로그인 없이 접근 가능한 페이지가 있고 로그인을 한 경우에만 접근이 가능한 페이지가 존재한다.

예를들어 마이페이지,비밀번호 변경,상품 등록하기 등이 있다.

 

Interceptor를 적용하기 전 로그인 확인 과정

  • 세션에서 현재 로그인된 사용자의 정보를 꺼내온다.
  • 세션에서 꺼낸 값이 Null이라면 , 해당 사용자는 로그인을 하지 않은 상태이므로 401 UNAUTHORIZED를 반환한다.
  • 만약 정상적으로 session에서 로그인 정보를 꺼낼수 있다면 200 OK 를 반환한다.

controller

1
2
3
4
5
6
7
@GetMapping("/my-info")
    public ResponseEntity<UserInfoDto> myInfo() {
        String currentUser = sessionloginService.getLoginUser();
        UserInfoDto userInfoDto = userService.getUserInfo(currentUser);
        return ResponseEntity.ok(loginUser);
    }
 
cs

 

service

1
2
3
4
5
6
7
public String getLoginUser() {
    String userId = session.getAttribute(USER_ID);
    if(userId == null) {
        throw new UnauthenticatedUserException();
    }
   return userId; 
}
cs

이제 이 과정을 Interceptor를 적용해서 해당 메소드가 자신의 핵심 기능만 집중할 수 있도록 리펙토링 해볼것이다.

 

어노테이션 생성 

 

1
2
3
4
@Retention(RUNTIME)
@Target(METHOD)
public @interface LoginCheck {
}
cs
  • @LoginCheck : 현재 사용자의 로그인 여부를 확인한다.
  • @Retention: 메모리를 어느 시점까지 가져갈지 여부 설정
  • @Target : 해당 어노테이션이 어느 위치까지 사용될지 지정한다.

 

Interceptor 정의

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Component
@RequiredArgsConstructor
public class LoginCheckInterceptor implements HandlerInterceptor {
 
    private final SessionLoginService loginService;
 
 
 
    //컨트롤러 메서드 실행되기전
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
 
       
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            LoginCheck loginCheck = handlerMethod.getMethodAnnotation(LoginCheck.class);
 
 
            if (loginCheck == null) {
                return true;
            }
 
 
            if (loginService.getLoginUser() == null) {
                throw new UnauthenticatedUserException("로그인 후 이용 가능합니다.");
            }
 
        }
    return true;
 
}
cs

Interceptor는 preHandler() , postHandler() , afterCompletion() 로 구성되어 있다.

  • preHandler() : 컨트롤러 메서드 실행되기전에 실행된다.
  • postHandler() : 컨트롤러 메서드 실행 직후, view가 렌더링 되기전 실행됨
  • afterCompletion() : view 페이지 렌더링 후 실행 

Interceptor는 메서드 실행 직후에 해당 요청을 가로채서 요청을 한 사용자의 로그인 여부를 판단한다.

loginCheck는 해당 Handler에 LoginCheck 어노테이션이 존재하는지 확인한다.

  • loginCheck가 null이라면 로그인 없이 접근 가능한 페이지 이므로 true를 반환한다.
  • loginCheck가 null이 아니라면 session에서 로그인 정보를 꺼내서 null 여부를 판단한다. null이라면 로그인 후 이용 가능하다는 Exception을 날린다.
  • 모든 검증을 통과했다면 로그인이 완료된 상태로 true를 리턴해 다음 작업을 실행한다.

이제 Interceptor를 등록한다.

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
 
    private final LoginCheckInterceptor loginCheckInterceptor;
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor);
    }
 
}
cs

 

WebMvcConfigurer는 스프링 부트가 기본적으로 설정한 MVC설정에 추가적으로 기능을 커스터마이징 할 수 있다.(스프링 부트 개발환경에서만 가능)

1
2
3
4
5
6
7
@LoginCheck
@GetMapping("/my-infos")
public ResponseEntity<UserInfoDto> myPage() {
   String currentUser = loginService.getLoginUser();
   UserInfoDto loginUser = userService.getUserInfo(currentUser);
   return ResponseEntity.ok(loginUser);
}
cs

이제 유저의 로그인 여부를 확인하고 사용자의 ID를 가져오는 Service에서 불필요한 예외처리를 할 필요가 없다.

 

728x90

'Dev > Spring' 카테고리의 다른 글

[Spring] JPA+Spring으로 카테고리 로직 구현 - 2  (0) 2021.12.10
[Spring] JPA+Spring으로 카테고리 로직 구현 - 1  (0) 2021.12.10
[Spring] AOP(Aspect Oriented Programming)  (0) 2021.09.10
ResponseEntity  (0) 2021.08.25
@Transactional  (1) 2021.08.24