ExceptionTranslationFilter

ExceptionTranslationFilter는 AuthenticationExceptionAccessDeniedException 이 두가지의 예외를 처리하는데 ExceptionTranslationFilter가 try-catch로 감싸고 catch에 오면 Filtersecurityinterceptor가 받아 예외를 발생시킨다.

 

AuthenticationException

  •  AuthenticationEntryPoint 호출한다.
  • 인증예외가 발생하기 전에 요청정보를 저장한다.

 

AccessDeniedException 

  • 인가 예외 처리 - AccessDeniedHandler에서 예외처리하도록 제공

 

 


 

설정

전체코드

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
			.antMatchers("/login").permitAll() 	// login 페이지는 인증이 안돼도 접근이 가능해야하기 때문에
			.antMatchers("/user").hasRole("USER")
			.antMatchers("/admin/pay").hasRole("ADMIN")
			.antMatchers("/admin/**").access("hasRole('ADMIN') or hasAnyRole('sys')")  //표현식을 사용해 두개이상의 권한을 설정
			.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 savedRequest = requestCache.getRequest(request, response);
						String redirectUrl = savedRequest.getRedirectUrl();
						response.sendRedirect(redirectUrl);
					}
				});

		http.exceptionHandling()
			.authenticationEntryPoint(new AuthenticationEntryPoint() {
				@Override
				public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
					response.sendRedirect("/login");
				}
			})
			.accessDeniedHandler(new AccessDeniedHandler() {
				@Override
				public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
					response.sendRedirect("/denied");
				}
			});

		return http.build();
	}
    
    @Bean
	public InMemoryUserDetailsManager userDetailsService() {
		List<UserDetails> users = new ArrayList<>();
		users.add(User.builder().username("user").password("{noop}1111").roles("USER").build());
		users.add(User.builder().username("sys").password("{noop}11111").roles("SYS").build());
		users.add(User.builder().username("admin").password("{noop}11111").roles("ADMIN", "USER").build());
		return new InMemoryUserDetailsManager(users);
	}
}

 

코드설명

AuthenticationException가 발생하면 RequestCache사용자의 이전 요청 정보을 세션에 저장하는데 그 요청했던 url를 불러오는 코드이다.

http
    .formLogin()
        .successHandler(new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                RequestCache requestCache = new HttpSessionRequestCache();
                SavedRequest savedRequest = requestCache.getRequest(request, response);
                String redirectUrl = savedRequest.getRedirectUrl();
                response.sendRedirect(redirectUrl);
            }
        });

 

인증 예외(AuthenticationException)이 발생했을 때 AuthenticationEntryPoint 를 호출하기 때문에 오버라이딩

http.exceptionHandling()
    .authenticationEntryPoint(new AuthenticationEntryPoint() {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            response.sendRedirect("/login");
        }
    })

 

 

인가 예외(AccessDeniedException)이 발생했을 때 AccessDeniedHandler를 호출하기 때문에 오버라이딩

http.exceptionHandling()
    .accessDeniedHandler(new AccessDeniedHandler() {
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            response.sendRedirect("/denied");
        }
    });

 

 


 

코드 플로우

인증 예외(AuthenticationException)

localhost:8080/ 으로 접속하면 인증에러가 발생한다. (1)은 인증에러 , (2)인가에러가 발생할 때 수행하게 된다. 

 

위 코드에서 sendStartAuthentication() 를 호출하면 (1) 캐쉬를 저장하고 (2) AuthenticationEntryPoint 호출한다.

 

캐쉬를 저장할 때 호출한 saveRequest 이다.  (1) 에서 세션에 요청정보를 저장하는 것을 확인할 수 있다.

 

 AuthenticationEntryPoint()가 호출하면 오버라이딩한 메소드가 호출된다.

http.exceptionHandling()
    .authenticationEntryPoint(new AuthenticationEntryPoint() {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            response.sendRedirect("/login");
        }
    })

 

이제 요청한 정보를 인증이후에 잘 캐쉬가 되었는지 확인해야한다. 테스트를 위해 아래처럼 주석처리해준다.

http.exceptionHandling()
    // .authenticationEntryPoint(new AuthenticationEntryPoint() {
    // 	@Override
    // 	public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    // 		response.sendRedirect("/login");
    // 	}
    // })
    .accessDeniedHandler(new AccessDeniedHandler() {
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            response.sendRedirect("/denied");
        }
    });

 

localhost:8080/user 는 USER 권한을 가진 유저만 접근할 수 있는데 접근하면 인증을 위해 login페이지로 이동한다. 로그인을 시도해보면 (1) onAuthenticationSuccess()가 호출되고 (2) 를 보면 처음 접근한 url를 캐쉬하고 있는걸 확인할 수 있다.

 

 

인가 예외(AccessDeniedException)

위 테스트를 이어서 localhost:8080/admin 은 ADMIN 권한을 가진 유저만 접근인 가능하다 접근해보면 (1) 이 호출된 것을 확인할 수 있다.

 

handle() 가 호출되면 오버라이딩한 메소드가 실행된다.

.accessDeniedHandler(new AccessDeniedHandler() {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.sendRedirect("/denied");
    }

 

 


 

RequestCacheAwareFilter

RequestCacheAwareFilter는 요청이 완료된 후, 즉 응답이 클라이언트로 보내진 후에, 보안 컨텍스트를 캐시합니다. 캐시된 보안 컨텍스트는 다음 요청에서 사용됩니다. 이는 캐시된 보안 컨텍스트가 더 이상 필요하지 않은 경우에만 캐시에서 삭제됩니다.


이 필터는 사용자가 로그인 후, 리디렉션된 후 다시 돌아올 때 캐시된 보안 컨텍스트를 사용하여 이전 상태를 복원할 때 유용합니다. 예를 들어, 로그인 하기 전에 요청한 페이지로 다시 리디렉션하는 경우, 캐시된 보안 컨텍스트를 사용하여 이전 상태를 복원할 수 있습니다.

public class RequestCacheAwareFilter extends GenericFilterBean {
  private RequestCache requestCache;

  public RequestCacheAwareFilter() {
    this(new HttpSessionRequestCache());
  }

  public RequestCacheAwareFilter(RequestCache requestCache) {
      Assert.notNull(requestCache, "requestCache cannot be null");
      this.requestCache = requestCache;
  }

  public void doFilter(ServletRequest request, ServletResponse response,
		FilterChain chain) throws IOException, ServletException {

    HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest(
      (HttpServletRequest) request, (HttpServletResponse) response);

    chain.doFilter(wrappedSavedRequest == null ? request : wrappedSavedRequest,
			response);
  }

}
  • doFilter 부분에서 requestCache.getMatchingRequest를 통해서 session에 저장되어 있는 request를 불러 온다.
  • 만약 캐시된 요청이 존재한다면, 캐시된 wrappedSavedRequest를 처리하고, 아닌 경우 request를 처리 한다.
  • 좀 더 예시를 들자면, 인증이 필요한 웹사이트에 접근했을 때 로그인창으로 리다이렉트 된 뒤 로그인을 하면 로그인 하기 이전에 페이지로 돌아 가는 것을 종종 볼 수 있다.
  • 로그인 하기전 요청을 캐시하고 있다가 로그인에 성공하는 경우 캐시된 요청을 로그인 이후에 처리해 주는 역할을 해주는게 RequestCacheAwareFilter 이다.

 

복사했습니다!