springframework/시작하자SpringSecurity

12.ExceptionTranslationFilter, RequestCacheAwareFilter

Jungsoomin :) 2020. 9. 21. 09:07

ExceptionTranslationFilter

크게 2가지의 Exception 을 처리한다.

  • AuthenticationException : 인증 예외

  • AccessDeniedException : 인가 예외


AuthenticationException , AccessDeniedException은 누가 Throw 할까.

 

FilterSecurityInterceptor 라는 필터이다. 

  • SpringSecurity의 보안필터 중 제일 마지막에 위치한다.

  • FilterSecurityInterceptor 앞에 위치한 FilterExceptionTranslationFilter 이다.

  1. ExceptionTranslationFilter Try-Catch 문으로 감싸서 FilterSecurityInterceptor 를 호출한다.

  2. FilterSecurityInterceptor  에서 발생한 AuthenticationException , AccessDeniedException 은 자신을 호출한 ExceptionTranslationFilter 에게 해당 Exception을 Throw 한다.


ExceptionTranslationFiler 의 AuthenticationException 처리

 

  1. SpringSecurityAuthenticationEntryPoint 인터페이스의 구현체를 제공

  • 로그인 페이지 이동 , 401( Unauthorized Error : 인증 자격 증명이 없음 ) 상태 코드 전달

  • AuthenticationEntryPoint 직접 구현하면 원하는 후속처리를 할 수 있다.

    2. 인증 예외가 발생하기 전 요청 정보를 저장

  • RequestCache : 사용자가 요청한 URL 정보 등을 가진 SavedRequest 객체를 Session에 저장하고 꺼내는 캐시 메커니즘을 가졌다. 로그인 페이지 에서 인증 성공 시 바로 요청했던 URL 로 이동시킨다.

  • SavedRequest : 사용자가 요청했던 RequestParameter , RequestHeader 를 저장, 실질적인 유저의 요청정보 를 가진다.


ExceptionTranslationFilter 의 AccessDeniedException 처리

 

  1. AccessDeniedHandler 에서 예외 처리를 하도록 함

  • AccessDeniedHandler 인터페이스를 구현한 구현체를 등록하면 메시지로 어떤 권한 필요한지의 후속처리를 할 수 있다.


 

ExceptionTranslationFilter 의 AuthenticationException 구체적 처리 방식

 

  1. 유저인증받지않고 특정 자원으로 Request

  2. FilterSecurityInterceptorAccessDeniedException 발생 ( 익명사용자 이므로 AnonymousAuthenticationFilterAnonymousAuthenticationToken 을 만든 상태)

  3. ExcetionTranslationFilter AccessDeniedExceptionCatch하지만, 익명사용자거나 (AnonymousAuthenticationFilterAnonymousAuthenticationToken) , RememberMe 기능(RememberMeAuthenticationFilterRememberMeServices가 만든 RememberMeAuthenticationToken)을 가지고 있을 경우 AuthenticationException 처리 과정으로 넘긴다.

  4. SecurityContextAuthentication 객체를 null로 처리

  5. AuthenticationEntryPoint의 구현체를 호출, AuthenticationEntryPoint로그인 페이지로 Redirect , 구현체가 있다면 후속 작업.

  6. RequestCache 의 구현체인 HttpSessionRequestCache 사용자의 Request 정보SavedRequest의 구현체인 DefaultSavedRequest에 저장, HttpSessionRequestCache를 다시 Session에 저장한다.


ExceptionTranslationFilter 의 AccessDeniedException 구체적 처리방식

 

  1. 유저가 인증을 한 상태에서 ROLE 에 맞지않는 자원으로의 Request

  2. FilterSecurityInterceptorAccessDeniedException 을 발생

  3. ExceptionTranslationFilter AccessDeniedExceptionCatch, AccessDeniedHandler 호출 

  4. AccessDeniedHandler /denied 페이지Redirect, AccessDeniedHandler 구현체가 있다면 후속 작업.


예외 처리를 위한 API들

http.exceptionHandling() : 예외처리 기능이 작동

 

   http.exceptionHandling() 의 하위 API

 

  1. authenticationEntryPoint(authenticationEntryPoint()) : 인증 실패 처리 구현객체, AuthenticationEntryPoint 구현객체가 온다.

  2. accessDeniedHandler(accessDeniedHanlder()) : 인가 실패 처리 구현객체, AccessDeniedHandler 구현 객체가 온다.

** 코드를 자세히보면, formLogin() 로그인 기능 활성화 하위 APIsuccessHanlder() 메서드에 AuthenticationSuccessHandler 구현체가 온 것을 볼 수 있는데, 여기서 RequestCache 의 구현체인 HttpSessionRequestCache 객체를 생성하여 SavedRequest 객체를 가져오고, SavedRequest 객체에서 getRedirectIrl()사용자가 요청했던 url 로 Redirect 시키는 것을 볼 수 있다.  

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user").password("{noop}1111").roles("USER");
        auth.inMemoryAuthentication().withUser("sys").password("{noop}1111").roles("USER","ADMIN");
        auth.inMemoryAuthentication().withUser("admin").password("{noop}1111").roles("USER","SYS","ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()//인가 요청들
                .antMatchers("/login").permitAll()//로그인 페이지는 모든 접근 허가
                .antMatchers("/user").hasRole("USER")//해당 경로에 대해 USER 권한 심사를 하겠다.
                .antMatchers("/admin/pay").hasRole("ADMIN")// 해당 경로에 대해 ADMIN 권한 심사를 하겠다.
                .antMatchers("/admin/**").access("hasRole('ADMIN') or hasRole('SYS')")// 해당 경로에 대해 SpEL 이 true 인지 권한 심사를 하겠다.
                .anyRequest()//어떤 요청이든
                .authenticated();//인증 성공시 접근가능하다.

        http
                .formLogin()
                .successHandler(new AuthenticationSuccessHandler() {//사용자의 인증 시 사용자가 요청했던 경로로 보내주는 구현
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                        RequestCache requestCache = new HttpSessionRequestCache();//SavedRequest 저장한 RequestCache 객체 생성.
                        SavedRequest savedRequest = requestCache.getRequest(request, response);//HttpSessionRequestCache 에서 유저 요청정보를 저장한 SavedRequest 객체 생성.
                        String redirectUrl = savedRequest.getRedirectUrl(); // SavedRequest 에서 사용자가 가고자하는 경로 추출
                        response.sendRedirect(redirectUrl);// 가고자 했던 경로로 Redirect
                    }
                });//폼 로그인 방식을 사용하겠다.

        http
                .exceptionHandling()// 인증, 인가 예외처리 기능 활성화
                .authenticationEntryPoint(new AuthenticationEntryPoint() {//AuthenticationException 발생시 처리
                    @Override
                    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                        System.out.println("인증 예외 발생 "+authException.getMessage());
                        response.setStatus(401);
                        response.sendRedirect("/login");
                    }
                })
                .accessDeniedHandler(new AccessDeniedHandler() {//AccessDeniedException 발생시 처리
                    @Override
                    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
                        System.out.println("인가 예외 발생 "+accessDeniedException.getMessage());
                        response.sendRedirect("/denied");
                    }
                });

    }
}

RequestCacheAwareFilter

  • 유저의 요청정보를 가진 SavedRequestRequestCache 에 저장하고 RequestCache 를 Session 담음

  • RequestCacheAwareFilterSavedRequest 존재여부를 확인하고 null 이 아닐경우 다음 Filter로 넘겨주는 역할을 한다.

  • 즉, 다른 Filter에서도 SavedRequest정보를 활용할 수 있도록 해준다.