본문 바로가기

백엔드/Spring

[Spring] 스프링 인터셉터를 이용한 세션 인증 구현

 

 

 🔍 서론

 

스프링 인터셉터(Spring Interceptor)를 이용해서 세션 인증을 하는 코드를 구현해 보았다. 스프링 인터셉터는 스프링 MVC의 핵심 부분으로, HTTP 요청이 컨트롤러에 도달하기 전과 후에 사용자가 정의한 특정 작업을 수행할 수 있게 해 준다. 이러한 인터셉터는 AOP(Aspect-Oriented Programming)의 개념과 유사하게 동작하며, 요청 처리 파이프라인에 횡단 관심사를 삽입하는 데 사용된다.

 

인터셉터의 구현 메서드 3개 중 하나인 preHandle은 컨트롤러의 호출 직전에 호출되어, 세션 인증과 같은 기능을 수행할 수 있다. 자세한 인터셉터의 흐름은 아래와 같다.

Http request -> Was -> Filter -> Servlet -> Spring Interceptor -> Controller

 

인터셉터 단계에서 미인증 사용자의 요청과 같은 부적절한 요청이 오면, 컨트롤러를 실행시키지 않고 인터셉터 단계에서 끝을 낼 수 있다.

 

이제 본격적으로 코드로 들어가 보겠다.

 

 

 

 

 💻 프로젝트

 

UserResponseDto

@PostMapping("/user/login")
    public UserResponseDto login(@RequestBody @Valid UserLoginDto requestUser, HttpServletRequest request) {
        log.info("(Controller) 로그인 시도");
        User loginUser = service.login(requestUser.getEmail(), requestUser.getPassword());

        log.info("(Controller) 로그인 성공, login userName = {}", loginUser.getUserName());
        HttpSession session = request.getSession();
        session.setAttribute("loginUser", loginUser);

        return new UserResponseDto(loginUser.getUserName());
    }

 

먼저, 로그인 기능을 수행하는 컨트롤러의 로그인 메서드이다.  로그인을 할 경우 session.setAttribute() 메서드로 세션에 객체를 등록하고, 세션 ID를 자체적으로 생성해서 클라이언트로 전송한다.

 

 

 

Postman으로 로그인 요청을 수행하면 위와 같이 응답 HTTP 헤더의 쿠기 값에 세션 ID가 들어있는 것을 확인할 수 있다.

 

 

LoginAuthInterceptor, InterceptorConfig

@Slf4j
public class LoginAuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("LoginAuthInterceptor 실행");

        HttpSession session = request.getSession(false);

        if (session==null || session.getAttribute("loginUser")==null) {
            log.info("미인증 사용자의 요청");
            throw new AuthFailerException("미인증 사용자의 요청");

        } else {
            return true;
        }
    }
}
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginAuthInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/user/creat", "/user/login");
    }
}

 

다음으로, HandlerInterceptor를 직접 구현해서 LoginAuthInterceptor를 만들고 WebMvcConfigurer를 구현해서 이 인터셉터의 적용 범위를 설정했다. 위의 인터셉터에서 세션 인가는 컨트롤러 호출 전에만 하면 되므로 preHandle만 구현했다. 내부 로직으로는 request의 세션을 확인해서 세션이 비었으면 직접 정의한 AuthFailerException 예외를 발생시키고, 이 예외를 @ExceptionHandler가 캐치하게 한다.

 

 

AuthExHandler

@ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(AuthFailerException.class)
    public ErrorResult AuthExHandler(AuthFailerException e) {
        log.info("AuthExHandler 실행, 에러 메시지 = {}", e.getMessage());
        return new ErrorResult("auth fail", e.getMessage());
    }

 

@RestControllerAdvice에 정의한 ExceptionHandler이다. AuthFailerException 에러를 캐치해서 401 UNAUTHORIZED 에러 코드를 응답으로 돌려준다.

 

@GetMapping("/authTest")
    public String authTest() {
        return "인증된 사용자 입니다.";
    }

 

인터셉터가 제대로 동작하는지 확인하려고 간단하게 Rest 컨트롤러에 GetMapping 메서드를 구현해 보았다. 위의 메서드는 조금 전에 지정한 인터셉터의 적용 범위에 해당하므로, 미인증 된 유저가 이 메서드로 요청을 보낼 경우 인터셉터에 의해 걸러져서 컨트롤러가 실행되지 않을 것이다. 

 

 

 

 

 🎛️ 동작확인 및 결론

 

 

세션 ID를 쿠키에 담지 않고 보냈을 경우, 위와 같이 401 상태 코드를 발행시키고 지정한 문구가 정상적으로 응답으로 돌아간다.

 

 

 

반면, 로그인을 하고 받은 세션 ID 쿠키에 담아서 방금과 동일한 요청을 했을 경우, 위와 같이 컨트롤러의 메서드에 지정한 반환 문구가 HTTP 바디에 담겨서 응답으로 돌아간다.