拥抱Spring全新OAuth解决方案(二)

Filer
SpringSecurity Filter 原理
Spring Auhorization Server 过滤器链配置


前言


上篇主要分析了Spring-Security-Oauth2-authorization-server 和 Spring Security Oauth2在应用上的对比和区别。本篇将从本质分析SpringSecurity的filter是如何工作的,这对于我们理清整个认证授权机制会有帮助。

1.filter

1.1 Servlet

在web应用中,Servlet 提供了Filter接口,过滤器按照某些规则,将请求拦截下来并做某些处理以后,再将请求交给Servlet。

拥抱Spring全新OAuth解决方案(二)

1.2 Spring Security Filter

SpringSecurity自己有一个叫FilterChainProxy代理类,该类也实现了Servlet接口。FilterChainProxy内部有一个List<SecurityFilterChain> filterChains

拥抱Spring全新OAuth解决方案(二)

SecurityFilterChain是一个接口也是一个chain,每个chain里有若干个filter.

拥抱Spring全新OAuth解决方案(二)

默认实现是DefaultSecurityFilterChain

拥抱Spring全新OAuth解决方案(二)

拥抱Spring全新OAuth解决方案(二)

这么多Filter,他们之间是如何协调工作的呢?

1.3 结论

 spring security里一个请求只会被一个filter chain进行处理。然后会被filter chain里的filter 依次处理。

拥抱Spring全新OAuth解决方案(二)

比如来了一个请求,url是:/foo/**,那么他会被会第一个filter chain处理,后面的两个filter chain会被忽略掉。


2.Spring Security Filter 原理

2.1 FilterChainProxy

继承自GenericFilterBean,而GenericFilterBean又实现了Javax.servlet.Filter

package org.springframework.security.web;

public class FilterChainProxy extends GenericFilterBean {

    private List<SecurityFilterChain> filterChains;

    public FilterChainProxy() {
    }

    public FilterChainProxy(SecurityFilterChain chain) {
        this(Arrays.asList(chain));
    }

    public FilterChainProxy(List<SecurityFilterChain> filterChains) {
        this.filterChains = filterChains;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        try {
            request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
            //执行真正过滤逻辑
            doFilterInternal(request, response, chain);
        }
        finally {
            SecurityContextHolder.clearContext();
            request.removeAttribute(FILTER_APPLIED);
        }
    }

接着看doFilterInternal


package org.springframework.security.web;

public class FilterChainProxy extends GenericFilterBean {

    private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
        HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
        //获取所有的filter
        List<Filter> filters = getFilters(firewallRequest);
        
        VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
        //内部代理类FilterChain执行过滤逻辑
        virtualFilterChain.doFilter(firewallRequest, firewallResponse);
    }

❶getFilters 获取所有的filter


package org.springframework.security.web;

public class FilterChainProxy extends GenericFilterBean {

     private List<Filter> getFilters(HttpServletRequest request) {
        int count = 0;
        for (SecurityFilterChain chain : this.filterChains) {
            //获取匹配的FilterChain
            if (chain.matches(request)) {
                //找到即返回该FilterChain的所有Filter
                return chain.getFilters();
            }
        }
        return null;
    }

❶chain.matches获取匹配的FilterChain

该FilterChain是SpringSecurity的SecurityFilterChain,默认实现是DefaultSecurityFilterChain

2.2 SecurityFilterChain


package org.springframework.security.web;

public final class DefaultSecurityFilterChain implements SecurityFilterChain {

    private final RequestMatcher requestMatcher;

    private final List<Filter> filters;
    
    @Override
    public List<Filter> getFilters() {
        return this.filters;
    }

    @Override
    public boolean matches(HttpServletRequest request) {
        return this.requestMatcher.matches(request);
    }

每个SecurityFilterChain内部都维护着一个RequestMatcher,来做请求路径的匹配。

这样每个每个请求只会被一个SecurityFilterChain处理。

回到doFilterInternal

❷交给virtualFilterChain来处理SecurityFilterChain的内部filter

2.3 VirtualFilterChain

它是FilterChainProxy的静态内部类

package org.springframework.security.web;

public class FilterChainProxy extends GenericFilterBean {

    private static final class VirtualFilterChain implements FilterChain {

        private final FilterChain originalChain;

        private final List<Filter> additionalFilters;

        private final FirewalledRequest firewalledRequest;

        private final int size;

        private int currentPosition = 0;

        @Override
        public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
            if (this.currentPosition == this.size) {
                this.firewalledRequest.reset();
                this.originalChain.doFilter(request, response);
                return;
            }
            //记录当前执行的offset
            this.currentPosition++;
            //获取下一个filter
            Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
            //执行filter,并将自身(FilterChain 传递下去,保证接下来的filter都能执行到)
            nextFilter.doFilter(request, response, this);
        }

    }    

❶通过集合的index获取接下来要执行的Filter

❷执行Filter ,并将自身VirtualFilterChain传递下去,保证下次还能执行VirtualFilterChain的doFilter方法,保证每个Filter都能执行到。

所以这个Filter的过程就是

拥抱Spring全新OAuth解决方案(二)

如果前一个FilterChain已经匹配,后面的FilterChain就不会再执行了。

2.4 FilterChain 和SecurityFilterChain的区别

上面出现了两个FilterChain,我们来简单对比一下

首先看Servlet的FilterChain


package javax.servlet;

public interface FilterChain {

    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;

是一个接口,然后只有一个方法。然后上面说的VirtualFilterChain实现了它

接着看Spring Security 的SecurityFilterChain

package org.springframework.security.web;

public interface SecurityFilterChain {

    boolean matches(HttpServletRequest request);

    List<Filter> getFilters();

}
SecurityFilterChain是满足Spring Security的多元配置而定义的。最终也是为了得到对应的所有Filters,然后被VirtualFilterChain包装,变成Servlet的FilterChain,然后调用Filter的doFilter,并将自身作为参数传递下去。如此循环,知道过滤器链中最后一个过滤器执行完成。

package javax.servlet;

public interface Filter {

    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain)
            throws IOException, ServletException;
从中我们也可以学到责任链模式(FilterChain),代理模式(FilterChainProxy,VirtualFilterChain)。以及SpringSecurity对于Filter扩展的一些思想。

3.Spring Authorization Server 的过滤器链

3.1 认证服务的FilterChain

我们首先看认证服务器的配置,同样还是以开源项目PIG来介绍。

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
    OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
    // 个性化认证授权端点
    http.apply(authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> {
        // 注入自定义的授权认证Converter
        tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter())
                // 登录成功处理器
                .accessTokenResponseHandler(new PigAuthenticationSuccessEventHandler())
                // 登录失败处理器
                .errorResponseHandler(new PigAuthenticationFailureEventHandler());
        // 个性化客户端认证        
    }).clientAuthentication(oAuth2ClientAuthenticationConfigurer ->
    // 处理客户端认证异常
    oAuth2ClientAuthenticationConfigurer.errorResponseHandler(new PigAuthenticationFailureEventHandler()))
            // 授权码端点个性化confirm页面
            .authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint
                    .consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)));

    RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
    DefaultSecurityFilterChain securityFilterChain = http.requestMatcher(endpointsMatcher)
            .authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
            // Redis存储token的实现
            .apply(authorizationServerConfigurer.authorizationService(authorizationService)
                    //token端口配置
                    .authorizationServerSettings(AuthorizationServerSettings.builder()
                            .issuer(SecurityConstants.PROJECT_LICENSE).build()))
            // 授权码登录的登录页个性化
            .and().apply(new FormIdentityLoginConfigurer()).and().build();


    // 注入自定义授权模式实现
    addCustomOAuth2GrantAuthenticationProvider(http);
    return securityFilterChain;
}
构造认证服务器的SecurityFilterChain。前面都是个性化配置,我们直接看蓝色部分,即构造我们的SecurityFilterChain

其中 AuthorizationServerSettings.builder()

public final class AuthorizationServerSettings extends AbstractSettings {

    public static Builder builder() {
    
        return new Builder()
                .authorizationEndpoint("/oauth2/authorize")
                .tokenEndpoint("/oauth2/token")
                .jwkSetEndpoint("/oauth2/jwks")
                .tokenRevocationEndpoint("/oauth2/revoke")
                .tokenIntrospectionEndpoint("/oauth2/introspect")
                .oidcClientRegistrationEndpoint("/connect/register")
                .oidcUserInfoEndpoint("/userinfo");
    }   


用来创建SecurtiyFilterChain 的RequestMatcher,也就是说只有这些uri才能被认证服务器的SecurityFilterChain匹配到,其他的请求会继续寻找匹配的SecurityFilterChain.

关于SecurtiyFilterChain的生成使用了创建者模式

    DefaultSecurityFilterChain securityFilterChain = http.requestMatcher(endpointsMatcher)
            .authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
            // redis存储token的实现
            .apply(authorizationServerConfigurer.authorizationService(authorizationService)
                    //token端口配置
                    .authorizationServerSettings(AuthorizationServerSettings.builder()
                            .issuer(SecurityConstants.PROJECT_LICENSE).build()))
            // 授权码登录的登录页个性化
            .and().apply(new FormIdentityLoginConfigurer()).and().build(); 
我们看最后的build()方法

package org.springframework.security.config.annotation;

public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {

    private AtomicBoolean building = new AtomicBoolean();

    private O object;

    @Override
    public final O build() throws Exception {
        if (this.building.compareAndSet(false, true)) {
            this.object = doBuild();
            return this.object;
        }
        throw new AlreadyBuiltException("This object has already been built");
    }

    
    protected abstract O doBuild() throws Exception;

}

接着调用doBuild()方法来执行真正的生成方法


package org.springframework.security.config.annotation;

public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
        extends AbstractSecurityBuilder<O> {

    @Override
    protected final O doBuild() throws Exception {
        synchronized (this.configurers) {
            this.buildState = BuildState.INITIALIZING;
            beforeInit();
            init();
            this.buildState = BuildState.CONFIGURING;
            beforeConfigure();
            configure();
            this.buildState = BuildState.BUILDING;
            O result = performBuild();
            this.buildState = BuildState.BUILT;
            return result;
        }
    }
}

最后调用performBuild()方法

实现有

拥抱Spring全新OAuth解决方案(二)

这里配置的入参用的是HttpSecurity

package org.springframework.security.config.annotation.web.builders;

public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
        implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
        
    private List<OrderedFilter> filters = new ArrayList<>();

    private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;

    private FilterOrderRegistration filterOrders = new FilterOrderRegistration();
        
    protected DefaultSecurityFilterChain performBuild() {
    
        ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer(
                ExpressionUrlAuthorizationConfigurer.class);
        AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class);
        boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null;
        Assert.state((expressionConfigurer == null && httpConfigurer == null) || oneConfigurerPresent,
                "authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one.");
        this.filters.sort(OrderComparator.INSTANCE);
        //Filter排序
        List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
        for (Filter filter : this.filters) {
            sortedFilters.add(((OrderedFilter) filter).filter);
        }
        // 创建SecurityFilterChain
        return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
    }    
        
        
        
       
先将filter排序,然后用ReuqestMatcher和排序好的Filter创建SecurityFilterChain.

最后我们通过断点看看最后生成的SecurityFilterChain包含的filter(19个)

拥抱Spring全新OAuth解决方案(二)

我们再看看requestMatcher(7个)

拥抱Spring全新OAuth解决方案(二)

也就是说该SecurityFilterChain是为这7个uri服务的。

这些都是可以自定义配置的。

那么其他的请求呢,比如访问某个资源?我们继续往下看。

3.2 资源服务的FilterChain

同样还是以开源项目PIG来介绍。

  @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                // 开放自定义的部分端点
                .authorizeRequests(authorizeRequests -> authorizeRequests.antMatchers("/token/*").permitAll()
                .anyRequest().authenticated())
                .headers()
                .frameOptions()
                .sameOrigin()// 避免iframe同源无法登录
                .and()
                 // 表单登录个性化
                .apply(new FormIdentityLoginConfigurer());
        // 处理 UsernamePasswordAuthenticationToken
        http.authenticationProvider(new PigDaoAuthenticationProvider());
        return http.build();
    }

public final class FormIdentityLoginConfigurer
        extends AbstractHttpConfigurer
<FormIdentityLoginConfigurer, HttpSecurity> {

    @Override
    public void init(HttpSecurity http) throws Exception {
        http.formLogin(formLogin -> {
            formLogin.loginPage("/token/login");
            formLogin.loginProcessingUrl("/token/form");
            formLogin.failureHandler(new FormAuthenticationFailureHandler());

        }).logout() // SSO登出成功处理
                .logoutSuccessHandler(new SsoLogoutSuccessHandler()).deleteCookies("JSESSIONID")
                .invalidateHttpSession(true).and().csrf().disable();
    }

}                .logoutSuccessHandler(new SsoLogoutSuccessHandler()).deleteCookies("JSESSIONID")
                .invalidateHttpSession(true).and().csrf().disable();
    }

}

anyRequest().authenticated(),可以看到除了开放的端点外,其他所有的端点都要鉴权。

最后也是通过HttpSecurity的build()方法生成。上面已经分析了该方法。我们自己通过断点看看最终的Filter.

拥抱Spring全新OAuth解决方案(二)

requestMatcher

拥抱Spring全新OAuth解决方案(二)

也就是说该SecurityFilerChain匹配所有的请求,如果前面的SecurityFilerChain不匹配,最终就会被该SecurityFilterChain执行。

3.3 最终的SecurityFilterChain

我们在前面已经分析了FilterChainProxy内部维护了一套SecurityFilterChains,我们直接看最终的FilterChain。

拥抱Spring全新OAuth解决方案(二)

可以看到认证服务器的排第一,静态资源服务器排第二,资源服务器排第三。

拥抱Spring全新OAuth解决方案(二)

那么顺序是如何定的呢?我们再回到代码

认证服务器,order是最高,排在最前。

拥抱Spring全新OAuth解决方案(二)

静态资源服务器,order为0,排在第二

拥抱Spring全新OAuth解决方案(二)

而资源服务器是默认的order(最低优先级),所以最靠后

拥抱Spring全新OAuth解决方案(二)


4. 总结


        当我们理清了认证服务和资源服务的过滤器链以后,接下来看具体的源码就会容易许多。同时在配置中,我们也尽量去分开配置,以免造成权限问题。


拥抱Spring全新OAuth解决方案(二)
关注我的你,是最香哒!


原文始发于微信公众号(小李的源码图):拥抱Spring全新OAuth解决方案(二)

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由半码博客整理,本文链接:https://www.bmabk.com/index.php/post/145268.html

(0)
青莲明月的头像青莲明月

相关推荐

发表回复

登录后才能评论
半码博客——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!