Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
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
Tags
more
Archives
Today
Total
관리 메뉴

걸음마부터 달리기

[24/11/12] 스프링 시큐리티 공식문서 요약 본문

카테고리 없음

[24/11/12] 스프링 시큐리티 공식문서 요약

성추 2024. 11. 13. 01:14

https://docs.spring.io/spring-security/reference/servlet/architecture.html

 

Architecture :: Spring Security

The Security Filters are inserted into the FilterChainProxy with the SecurityFilterChain API. Those filters can be used for a number of different purposes, like authentication, authorization, exploit protection, and more. The filters are executed in a spec

docs.spring.io

 

DelegatingFilterProxy

DelegatingFilterProxy라는 Filter를 지원한다. 이는 서블릿 컨테이너와 스프링의 ApplicationContext, 즉 컨테이너와 생명주기를 같이 한다. 서블릿 컨테이너는 그 자체의 생성 표준으로 filter 인스턴스를 만들고 등록하는데 아무래도 이는 스프링 컨테이너와는 관련이 없다보니 이 Filter를 빈으로 사용하지 못했다. 그래서 나온게 DelegatingFilterProxy이고 얘를 통해서 Filter를 구현한 인스턴스들은 스프링 빈으로 등록되면서 빈이지만 필터처럼 동작이 가능하다는 것이다. 

DelegatingFilterProxy 가 서블릿 컨테이너에서 맞는 필터 빈에게 찾아 위임해줘서 가능

 

또한 얘로 인해서 필터를 서블릿 컨테이너가 띄워지면서 바로 등록했었어야 했는데 이제는 delegatingFilterProxy에서 스프링 빈한테 위임할거니까 굳이 서블릿 컨테이너 초기화될때 서둘러 등록할 필요 없이 좀 기다렸다가 이후 스프링 컨테이너 초기화 시점의 빈 등록때 필터 빈 등록시키면 되는거라 lazy loading이 가능하다는 거다.

FilterChainProxy

FilterChainProxy라는 많은 필터에게 delegating하는 특별한 필터를 지원한다. 얘가 여러 필터를 품고 있고 알맞는 filter에게 위임하는 것을 허용한다. 얘도 빈이다. 

SecurityFilterChain

얘는 FilterChainProxy가 어떤 필터에게 위임해줘야 하는지 결정해주는 놈이다. 

  • FilterChainProxy가 어떤 Spring Security 필터를 호출할지 결정하는 데 사용된다.
  • 일반적인 서블릿 필터와 달리 URL 뿐만 아니라 HttpServletRequest의 모든 정보를 기반으로 필터 적용 여부를 결정할 수 있다. 즉 SecurityFilterChain의 조건을 보고 FilterChainProxy가 맞는 놈한테 위임함.

DelegatingProxy가 아닌 FilterchainProxy가 빈으로 등록되어 있다. 

 

SecurityFilterChain 내의 보안 필터들은 일반적으로 빈(Beans)이지만, DelegatingFilterProxy가 아닌 FilterChainProxy에 등록된다.

 

즉 종합하면 이런 느낌이다.

  • 일반적인 Spring 빈들은 DelegatingFilterProxy를 통해 서블릿 컨테이너의 필터 체인에 연결됨.
  • 그러나 Spring Security의 필터들은 FilterChainProxy에 직접 등록됨
  • FilterChainProxy는 Spring Security의 모든 보안 필터들을 관리하고 조정하는 중앙 집중화된 지점임
  • FilterChainProxy에 등록된 SecurityFilterChain의 빈 필터를 통해 수행됨

Adding a Custom Filter to the Filter Chain

public class TenantFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        String tenantId = request.getHeader("X-Tenant-Id"); 
        boolean hasAccess = isUserAllowed(tenantId); 
        if (hasAccess) {
            filterChain.doFilter(request, response); 
            return;
        }
        throw new AccessDeniedException("Access denied"); 
    }

}

위처럼 해당 필터 다음에 doFilter를 통해 다음 필터 빈을 수행시키면 되는거고 

안되면 이제 예외 던지면 된다.

 

당연하게도 이런 필터는 SecurityFilterChain에 등록되어야 하므로

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        // ...
        .addFilterBefore(new TenantFilter(), AuthorizationFilter.class); //여기
    return http.build();
}

 추가하면 된다.

 

조심해야될건 필터 자체를 필터 자체를 @Component 처럼 빈 등록 어노테이션 붙혀버리면 

DelegatingFilterProxyRegistrationBean 가 내부적으로 빈 등록함과 동시에

스프링 컨테이너 자체적으로 또 빈으로 등록해버린다. 

그래서 빈 등록이 2번 되기에 (원래는 서로 이름 같으면 빈 등록이 불가능한데 뭐 알아서 이름 바꿔서 등록해주지 않을까)

해당 필터 동작이 2번이 될 수 있다는 것이다. 

 

Handling Security Exceptions

빈 필터 만들면서 예외를 던졌었다. ExceptionTranslationFilter는 AccessDeniedException과 AutheticationException 예외에 대해서 처리해준다. 

ExceptionTranslationFilter 에서 우선 FilterChain.doFilter로 남은 필터를 수행한다.

남은 필터들이 수행되면서 예외가 생긴다면 우선 ExceptionTranslationFilter 까지 올라온다.

그 예외가 AuthenticationException이나 AccessDeniedException이면 여기서 처리한다. 

우선 예외 먼저 보면

  1. AuthenticationException:
    • 인증(Authentication) 과정에서 발생하는 예외입니다.
    • 사용자가 아직 인증되지 않았거나 인증 과정에서 문제가 발생했을 때 발생합니다.
    • 예: 잘못된 아이디/비밀번호, 계정 잠김, 인증 토큰 만료 등
    • 주로 AuthenticationEntryPoint에 의해 처리됩니다.
    • 일반적으로 로그인 페이지로 리다이렉트하거나 401 Unauthorized 응답을 반환합니다.
  2. AccessDeniedException:
    • 인가(Authorization) 과정에서 발생하는 예외입니다.
    • 사용자가 이미 인증되었지만 특정 리소스에 접근할 권한이 없을 때 발생합니다.
    • 예: 일반 사용자가 관리자 전용 페이지에 접근 시도
    • 주로 AccessDeniedHandler에 의해 처리됩니다.
    • 일반적으로 403 Forbidden 응답을 반환하거나 접근 거부 페이지로 리다이렉트합니다.

이 둘의 처리방식은

    1. 인증 예외 (AuthenticationException) 처리:
      • 사용자가 인증되지 않은 경우 발생합니다.
      • AuthenticationEntryPoint를 호출하여 로그인 페이지로 리다이렉트하거나 401 응답을 보냅니다.
      • SecurityContext를 초기화합니다.
  1. 접근 거부 예외 (AccessDeniedException) 처리:
    • 인증된 사용자가 접근 권한이 없는 리소스에 접근할 때 발생합니다.
    • AccessDeniedHandler를 호출하여 403 응답을 보내거나 접근 거부 페이지로 리다이렉트합니다.

휴도코드는 이렇댄다.

try {
	filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
	if (!authenticated || ex instanceof AuthenticationException) {
		startAuthentication();
	} else {
		accessDenied();
	}
}

 

 

 

Saving Requests Between Authentication

인증이 안되어서 인증을 받아야할때는 이러한 기존의 request를 인증 후에 다시 재전송하기 위해 저장할 필요가 있다.

스프링 시큐리티는 이것을 RequestCache 구현체에 의해 저장된다.  

  • HttpServletRequest 객체의 정보를 SavedRequest 객체로 변환하여 저장합니다.
  • 기본적으로 HttpSessionRequestCache를 사용하여 HTTP 세션에 저장합니다.

성공적으로 인증되었으면 이 RequestCache를 보고 기존의 request를 다시 보낸다. 

이것을 ExceptionTranslationFilter가 AuthenticationException의 예외가 온 순간 RequestCache에 기존 HttpServletRequest를 저장하기에 사용하고 RequestCacheAwareFilter가 그것을 그대로 가져다 쓴다.  

 

RequestCache도 커스텀해서 쓸 수 있다.

@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
	HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
	requestCache.setMatchingRequestParameterName("continue");
	http
		// ...
		.requestCache((cache) -> cache
			.requestCache(requestCache)
		);
	return http.build();
}

 

 

너는 이러한 인증 전 요청을 저장하기 싫을 수도 있다. 이때에는

@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    RequestCache nullRequestCache = new NullRequestCache();
    http
        // ...
        .requestCache((cache) -> cache
            .requestCache(nullRequestCache)
        );
    return http.build();
}

처럼 NullRequestCache 구현체를 사용하면 된다.