Programing

스프링 보안 필터 체인의 작동 원리

lottogame 2020. 8. 16. 21:43
반응형

스프링 보안 필터 체인의 작동 원리


나는 Spring 보안이 요청을 가로 채고, 인증을 감지하고, 인증 진입 점으로 리디렉션하거나, 요청을 인증 서비스에 전달하고, 결국 요청이 서블릿에 도달하거나 보안 예외를 던지도록하는 필터 체인을 기반으로 구축된다는 것을 알고 있습니다. (인증되지 않았거나 승인되지 않음). DelegatingFitlerProxy는 이러한 필터를 함께 붙입니다. 작업을 수행하기 위해 이러한 필터는 UserDetailsServiceAuthenticationManager 와 같은 서비스에 액세스합니다 .

체인의 주요 필터는 다음과 같습니다 (순서대로).

  • SecurityContextPersistenceFilter (JSESSIONID에서 인증 복원)
  • UsernamePasswordAuthenticationFilter (인증 수행)
  • ExceptionTranslationFilter (FilterSecurityInterceptor에서 보안 예외 포착)
  • FilterSecurityInterceptor (인증 및 권한 부여 예외 발생 가능)

이 필터가 어떻게 사용되는지 혼란 스럽습니다. Spring에서 제공되는 form-login의 경우 UsernamePasswordAuthenticationFilter/ login 에만 사용 되며 후자의 필터는 사용되지 않습니까? 않는 폼 로그인 네임 스페이스 요소는 이러한 필터를 자동으로 구성? 모든 요청 (인증 여부)이 비 로그인 URL에 대해 FilterSecurityInterceptor도달 합니까?

로그인에서 검색되는 JWT-token으로 REST API를 보호하려면 어떻게해야 합니까? 두 개의 네임 스페이스 구성 http태그, 권한을 구성해야 합니까? 다른 하나는 / login with UsernamePasswordAuthenticationFilter이고 다른 하나는 custom JwtAuthenticationFilter.

http요소를 구성하면 두 가지가 생성 springSecurityFitlerChains됩니까? 인가 UsernamePasswordAuthenticationFilter내가 선언 할 때까지 기본적으로 꺼져 form-login? 대신 기존에서 SecurityContextPersistenceFilter얻을 수있는 것으로 어떻게 대체 합니까?AuthenticationJWT-tokenJSESSIONID


Spring 보안 필터 체인은 매우 복잡하고 유연한 엔진입니다.

체인의 주요 필터는 다음과 같습니다 (순서대로).

  • SecurityContextPersistenceFilter (JSESSIONID에서 인증 복원)
  • UsernamePasswordAuthenticationFilter (인증 수행)
  • ExceptionTranslationFilter (FilterSecurityInterceptor에서 보안 예외 포착)
  • FilterSecurityInterceptor (인증 및 권한 부여 예외 발생 가능)

상기 찾고 현재 안정적인 릴리스 4.2.1 문서 , 섹션 13.3 필터 순서 는 전체 필터 체인의 필터 구성을 볼 수 있었다 :

13.3 필터 순서

체인에서 필터가 정의되는 순서는 매우 중요합니다. 실제로 사용중인 필터에 관계없이 순서는 다음과 같아야합니다.

  1. ChannelProcessingFilter , 다른 프로토콜로 리디렉션해야 할 수 있기 때문입니다.

  2. SecurityContextPersistenceFilter- 웹 요청 시작시 SecurityContextHolder에서 SecurityContext를 설정할 수 있으며, 웹 요청이 종료 될 때 SecurityContext에 대한 모든 변경 사항을 HttpSession에 복사 할 수 있습니다 (다음 웹 요청에 사용할 준비가 됨).

  3. ConcurrentSessionFilter , SecurityContextHolder 기능을 사용하고 주체의 지속적인 요청을 반영하도록 SessionRegistry를 업데이트해야하기 때문입니다.

  4. 인증 처리 메커니즘 - UsernamePasswordAuthenticationFilter , CasAuthenticationFilter , BasicAuthenticationFilter 등 - 그래서 SecurityContextHolder에 유효한 인증 요청 토큰을 포함하도록 수정 될 수 있음

  5. SecurityContextHolderAwareRequestFilter , 당신은 당신의 서블릿 컨테이너에 HttpServletRequestWrapper 스프링 보안 인식을 설치하는 데 사용하는 경우

  6. JaasApiIntegrationFilter는 경우, JaasAuthenticationToken가 SecurityContextHolder에있는이는 JaasAuthenticationToken의 주제로 FilterChain을 처리합니다

  7. RememberMeAuthenticationFilter- 이전 인증 처리 메커니즘이 SecurityContextHolder를 업데이트하지 않았고 요청이 remember-me 서비스를 수행 할 수있는 쿠키를 제공하는 경우 적절한 기억 된 인증 오브젝트가 거기에 배치됩니다.

  8. AnonymousAuthenticationFilter- 이전 인증 처리 메커니즘이 SecurityContextHolder를 업데이트하지 않은 경우 익명 인증 개체가 거기에 배치됩니다.

  9. ExceptionTranslationFilter , HTTP 오류 응답이 반환되거나 적절한 AuthenticationEntryPoint가 시작될 수 있도록 Spring Security 예외를 포착합니다.

  10. FilterSecurityInterceptor , 웹 URI를 보호하고 액세스가 거부 될 때 예외를 발생시킵니다.

이제 여러분의 질문을 하나씩 살펴 보겠습니다.

이 필터가 어떻게 사용되는지 혼란 스럽습니다. 스프링 제공 양식 로그인의 경우 UsernamePasswordAuthenticationFilter가 / login에만 사용되고 후자의 필터는 사용되지 않습니까? 양식 로그인 네임 스페이스 요소가 이러한 필터를 자동 구성합니까? 모든 요청 (인증 여부)이 비 로그인 URL에 대해 FilterSecurityInterceptor에 도달합니까?

<security-http>섹션을 구성하고 나면 각 섹션에 대해 하나 이상의 인증 메커니즘을 제공해야합니다. 이것은 내가 방금 참조한 Spring Security 문서의 13.3 Filter Ordering 섹션에서 그룹 4와 일치하는 필터 중 하나 여야합니다.

다음은 구성 할 수있는 최소 유효한 security : http 요소입니다.

<security:http authentication-manager-ref="mainAuthenticationManager" 
               entry-point-ref="serviceAccessDeniedHandler">
    <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
</security:http>

이렇게하면 다음 필터가 필터 체인 프록시에 구성됩니다.

{
        "1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
        "2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
        "3": "org.springframework.security.web.header.HeaderWriterFilter",
        "4": "org.springframework.security.web.csrf.CsrfFilter",
        "5": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
        "6": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
        "7": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
        "8": "org.springframework.security.web.session.SessionManagementFilter",
        "9": "org.springframework.security.web.access.ExceptionTranslationFilter",
        "10": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
    }

Note: I get them by creating a simple RestController which @Autowires the FilterChainProxy and returns it's contents:

    @Autowired
    private FilterChainProxy filterChainProxy;

    @Override
    @RequestMapping("/filterChain")
    public @ResponseBody Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
        return this.getSecurityFilterChainProxy();
    }

    public Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
        Map<Integer, Map<Integer, String>> filterChains= new HashMap<Integer, Map<Integer, String>>();
        int i = 1;
        for(SecurityFilterChain secfc :  this.filterChainProxy.getFilterChains()){
            //filters.put(i++, secfc.getClass().getName());
            Map<Integer, String> filters = new HashMap<Integer, String>();
            int j = 1;
            for(Filter filter : secfc.getFilters()){
                filters.put(j++, filter.getClass().getName());
            }
            filterChains.put(i++, filters);
        }
        return filterChains;
    }

Here we could see that just by declaring the <security:http> element with one minimum configuration, all the default filters are included, but none of them is of a Authentication type (4th group in 13.3 Filter Ordering section). So it actually means that just by declaring the security:http element, the SecurityContextPersistenceFilter, the ExceptionTranslationFilter and the FilterSecurityInterceptor are auto-configured.

In fact, one authentication processing mechanism should be configured, and even security namespace beans processing claims for that, throwing an error during startup, but it can be bypassed adding an entry-point-ref attribute in <http:security>

If I add a basic <form-login> to the configuration, this way:

<security:http authentication-manager-ref="mainAuthenticationManager">
    <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
    <security:form-login />
</security:http>

Now, the filterChain will be like this:

{
        "1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
        "2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
        "3": "org.springframework.security.web.header.HeaderWriterFilter",
        "4": "org.springframework.security.web.csrf.CsrfFilter",
        "5": "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter",
        "6": "org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter",
        "7": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
        "8": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
        "9": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
        "10": "org.springframework.security.web.session.SessionManagementFilter",
        "11": "org.springframework.security.web.access.ExceptionTranslationFilter",
        "12": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
    }

Now, this two filters org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter and org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter are created and configured in the FilterChainProxy.

So, now, the questions:

Is it that for the spring provided form-login, UsernamePasswordAuthenticationFilter is only used for /login, and latter filters are not?

Yes, it is used to try to complete a login processing mechanism in case the request matches the UsernamePasswordAuthenticationFilter url. This url can be configured or even changed it's behaviour to match every request.

You could too have more than one Authentication processing mechanisms configured in the same FilterchainProxy (such as HttpBasic, CAS, etc).

Does the form-login namespace element auto-configure these filters?

No, the form-login element configures the UsernamePasswordAUthenticationFilter, and in case you don't provide a login-page url, it also configures the org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter, which ends in a simple autogenerated login page.

The other filters are auto-configured by default just by creating a <security:http> element with no security:"none" attribute.

Does every request (authenticated or not) reach FilterSecurityInterceptor for non-login url?

Every request should reach it, as it is the element which takes care of whether the request has the rights to reach the requested url. But some of the filters processed before might stop the filter chain processing just not calling FilterChain.doFilter(request, response);. For example, a CSRF filter might stop the filter chain processing if the request has not the csrf parameter.

What if I want to secure my REST API with JWT-token, which is retrieved from login? I must configure two namespace configuration http tags, rights? Other one for /login with UsernamePasswordAuthenticationFilter, and another one for REST url's, with custom JwtAuthenticationFilter.

No, you are not forced to do this way. You could declare both UsernamePasswordAuthenticationFilter and the JwtAuthenticationFilter in the same http element, but it depends on the concrete behaviour of each of this filters. Both approaches are possible, and which one to choose finnally depends on own preferences.

Does configuring two http elements create two springSecurityFitlerChains?

Yes, that's true

Is UsernamePasswordAuthenticationFilter turned off by default, until I declare form-login?

Yes, you could see it in the filters raised in each one of the configs I posted

How do I replace SecurityContextPersistenceFilter with one, which will obtain Authentication from existing JWT-token rather than JSESSIONID?

You could avoid SecurityContextPersistenceFilter, just configuring session strategy in <http:element>. Just configure like this:

<security:http create-session="stateless" >

Or, In this case you could overwrite it with another filter, this way inside the <security:http> element:

<security:http ...>  
   <security:custom-filter ref="myCustomFilter" position="SECURITY_CONTEXT_FILTER"/>    
</security:http>
<beans:bean id="myCustomFilter" class="com.xyz.myFilter" />

EDIT:

One question about "You could too have more than one Authentication processing mechanisms configured in the same FilterchainProxy". Will the latter overwrite the authentication performed by first one, if declaring multiple (Spring implementation) authentication filters? How this relates to having multiple authentication providers?

This finally depends on the implementation of each filter itself, but it's true the fact that the latter authentication filters at least are able to overwrite any prior authentication eventually made by preceding filters.

But this won't necesarily happen. I have some production cases in secured REST services where I use a kind of authorization token which can be provided both as a Http header or inside the request body. So I configure two filters which recover that token, in one case from the Http Header and the other from the request body of the own rest request. It's true the fact that if one http request provides that authentication token both as Http header and inside the request body, both filters will try to execute the authentication mechanism delegating it to the manager, but it could be easily avoided simply checking if the request is already authenticated just at the begining of the doFilter() method of each filter.

Having more than one authentication filter is related to having more than one authentication providers, but don't force it. In the case I exposed before, I have two authentication filter but I only have one authentication provider, as both of the filters create the same type of Authentication object so in both cases the authentication manager delegates it to the same provider.

And opposite to this, I too have a scenario where I publish just one UsernamePasswordAuthenticationFilter but the user credentials both can be contained in DB or LDAP, so I have two UsernamePasswordAuthenticationToken supporting providers, and the AuthenticationManager delegates any authentication attempt from the filter to the providers secuentially to validate the credentials.

So, I think it's clear that neither the amount of authentication filters determine the amount of authentication providers nor the amount of provider determine the amount of filters.

Also, documentation states SecurityContextPersistenceFilter is responsible of cleaning the SecurityContext, which is important due thread pooling. If I omit it or provide custom implementation, I have to implement the cleaning manually, right? Are there more similar gotcha's when customizing the chain?

I did not look carefully into this filter before, but after your last question I've been checking it's implementation, and as usually in Spring, nearly everything could be configured, extended or overwrited.

The SecurityContextPersistenceFilter delegates in a SecurityContextRepository implementation the search for the SecurityContext. By default, a HttpSessionSecurityContextRepository is used, but this could be changed using one of the constructors of the filter. So it may be better to write an SecurityContextRepository which fits your needs and just configure it in the SecurityContextPersistenceFilter, trusting in it's proved behaviour rather than start making all from scratch.


UsernamePasswordAuthenticationFilter is only used for /login, and latter filters are not?

No, UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter, and this contains a RequestMatcher, that means you can define your own processing url, this filter only handle the RequestMatcher matches the request url, the default processing url is /login.

Later filters can still handle the request, if the UsernamePasswordAuthenticationFilter executes chain.doFilter(request, response);.

More details about core fitlers

Does the form-login namespace element auto-configure these filters?

UsernamePasswordAuthenticationFilter is created by <form-login>, these are Standard Filter Aliases and Ordering

Does every request (authenticated or not) reach FilterSecurityInterceptor for non-login url?

It depends on whether the before fitlers are successful, but FilterSecurityInterceptor is the last fitler normally.

Does configuring two http elements create two springSecurityFitlerChains?

Yes, every fitlerChain has a RequestMatcher, if the RequestMatcher matches the request, the request will be handled by the fitlers in the fitler chain.

The default RequestMatcher matches all request if you don't config the pattern, or you can config the specific url (<http pattern="/rest/**").

If you want to konw more about the fitlers, I think you can check source code in spring security. doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

참고URL : https://stackoverflow.com/questions/41480102/how-spring-security-filter-chain-works

반응형