앞서 만들었던 MemberController에 대한 로직들은 로그인이 없어도 호출이 가능했고, 비록 다른 사용자여도 로그인만 하여 세션이 있다면 다른 사용자의 정보를 이용할 수 있었습니다. 이러한 현상을 방지하기 위해 Interceptor를 만들도록 하겠습니다.

 

Interceptor란?

Controller의 Handler를 호출하기 전, 호출한 후를 가로채서 가공을 할 수 있는 것을 의미합니다.

기본적인 spring Interceptor의 흐름은 아래와 같습니다.

 

1. MemberInterceptor

@Component
@RequiredArgsConstructor
public class MemberInterceptor implements HandlerInterceptor {

    private final UserRepository userRepository;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);

        if(session==null){
            response.sendRedirect("/err/member/noUser");
            return false;
        }
        SessionDto sessionUser = (SessionDto) session.getAttribute(SessionConst.LOGIN_MEMBER);
        User user = userRepository.findById(sessionUser.getId()).orElseThrow(
                () -> {
                    throw new CanNotFindUser("해당 유저는 없습니다.");
                }
        );

        if(sessionUser.getRole()!=ROLE.MEMBER){
            response.sendRedirect("/err/member/notAuthority");
            return false;
        }
        return true;
    }
}
  • HandlerInterceptor: preHadler, postHandler, afterHandler 총 3가지를 구현할 수 있지만 저희는 로그인을 하여 세션이 있는지, 역할은 Member인지만 확인하여 Controller로 보내면 되게 때문에 preHandler만 Override 해서 구현합니다.
  • response.sendRedirect(): 만약 session이 없다면 로그인 되지 않은 사용자이기 때문에 요청을 /err/member로 redirect 하고, return false를 하여 원래 URI에 도달하지 못하게 합니다.
  • sessoion은 있지만 역할이 Member가 아닐 때는 resposen.sendRedirect("/err/member/notAuthority")로 보냅니다.
  • 위의 두 조건을 모두 만족한다면 원래 URI을 담당하는 Controller에게 요청이 가게됩니다.
  • 유의 사항은 response.sendRedirect를 하여도 밑에 로직들은 계속해서 수행되게 됩니다. 따라서 return false로 바로 밑에 로직들을 수행하는 것을 차단해줘야 합니다.

2. CanNotFindUser

public class CanNotFindUser extends RuntimeException{
    protected CanNotFindUser(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    public CanNotFindUser() {
        super();
    }

    public CanNotFindUser(String message) {
        super(message);
    }

    public CanNotFindUser(String message, Throwable cause) {
        super(message, cause);
    }

    public CanNotFindUser(Throwable cause) {
        super(cause);
    }
}
  • MemberInterceptor의 UserRepository에서 혹시나 User가 없는 예외가 터질 것을 대비해서 추가한 예외 클래스입니다.

3. ErrorController

@RestController
@RequestMapping("/err")
public class ErrorController {


    @GetMapping("/member/noUser")
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorDto noLogin(){
        return new ErrorDto("로그인을 해주세요.");
    }

    @GetMapping("/member/notAuthority")
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorDto noAuthority(){
        return new ErrorDto("해당 유저는 멤버 권한이 없습니다.");
    }
}
  • Interceptor에서 redirect가 발생한 것을 담당하는 Controller입니다.
  • 발생한 이유를 담아 클라이언트에게 응답합니다.

4. WebConfig 수정

@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

    private final UserRepository userRepository;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new ArgumentResolver());
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MemberInterceptor(userRepository))
                .addPathPatterns("/member/**")
                .excludePathPatterns("/signup","/login");
    }
}
  • addInterceptors를 Override 하여 추가해줍니다.
  • addPathPatterns(): Interceptor를 적용할 URI을 명시합니다.
  • excludePathPatterns: Interceptor를 적용하지 않을 URI을 명시합니다. /signup, /login은 session이 없어도 사용할 수 있어야 회원가입과 로그인이 가능하므로 예외 패턴에 넣습니다.

 

5. Postman Test

  • 로그인을 하지 않아 session이 없지만 /member/**에 접근을 시도했을 때

 

MemberInterceptor에서 Session이 없는 것을 확인하여 요청받은 URI가 아닌 Error URI로 redirect 한 것을 볼 수 있습니다.
  • 역할이 Member가 아닌 TeamFounder인데  Member URI에 접근했을 때

Member가 아닌 founder로 회원가입을 진행했습니다.

로그인을 하여 session이 생성된 것을 알 수 있습니다.

ROLE이 Member가 아니기 때문에 오류를 반환하는 것을 알 수 있습니다. 

 

6. 추가된 패키지 구성입니다.

지금까지 MemberController에 접근하는 데 있어서 Interceptor로 요청을 가로채 정보를 확인한 후 로그인을 하였는지, Member의 역할을 가졌는지 확인해보았습니다. 다음 포스팅은 Team, League에 관한 Repository, Service, Controller, Interceptor를 만듭니다. 중복되는 내용이 많으므로 빠르게 제작하고 그다음에는 AOP에 관하여 포스팅하겠습니다. 감사합니다. 

 

모든 코드는 아래 링크에서 확인 가능합니다.

https://github.com/rlaehdals/blogProject

 

GitHub - rlaehdals/blogProject

Contribute to rlaehdals/blogProject development by creating an account on GitHub.

github.com