SpringSecurity | spring security oauth2.0 配置源码分析(二)


继上一篇《SpringSecurity | spring security oauth2.0 配置源码分析(一)》简单的分析配置之后,今天从源码的角度来分析配置是如何生效的,Oauth2.0如何和 Spring Security 整合的。

1)先看下Spring Security中 HttpSecurity配置:

在上一篇配置讲解中,我们提到了oauth2两个注解配置:

   //配置授权资源路径
    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter
    //配置认证服务
    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends    AuthorizationServerConfigurerAdapter

继续跟进这两个注解,找到两个关键类:
AuthorizationServerSecurityConfiguration,ResourceServerConfiguration
这两个类是 spring security接口SecurityConfigurer的子类,在项目启动的时候,spring security 会初始化SecurityConfigurer的子类,所以在加载自身配置 WebSecurityConfig 的同时,会将这两个oauth2的配置类一并初始化,然后针对配置类进行加载对应的配置。

断点可以很清晰看到,配置集合 configures中有如下三个配置类:
这里写图片描述
其中第一个和第三个属于oauth2的配置。

接下来重点说一下这三个配置类。
首先这里采用了模板模式,抽象了部分相同的逻辑,也就是构造httpSecurity对象的逻辑。来具体看一下,在父类WebSecurityConfigurerAdapter 代码如下:

protected final HttpSecurity getHttp() throws Exception {
        if (http != null) {
            return http;
        }

        DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
                .postProcess(new DefaultAuthenticationEventPublisher());
        localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

        AuthenticationManager authenticationManager = authenticationManager();
        authenticationBuilder.parentAuthenticationManager(authenticationManager);
        Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();

        http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
                sharedObjects);
        if (!disableDefaults) {

            //可以看到and()分割后的每段配置,实际上都是在HttpSecuirty中添加一个过滤器,
            //而每个过滤器对应一个 configurer,将过滤器加入到容器中的前提,是先将configurer初始化。
            //这点对下面配置很重要。
            http
                .csrf().and()
                .addFilter(new WebAsyncManagerIntegrationFilter())
                .exceptionHandling().and()
                .headers().and()
                .sessionManagement().and()
                .securityContext().and()
                .requestCache().and()
                .anonymous().and()
                .servletApi().and()
                .apply(new DefaultLoginPageConfigurer<HttpSecurity>()).and()
                .logout();
            // @formatter:on
            ClassLoader classLoader = this.context.getClassLoader();
            List<AbstractHttpConfigurer> defaultHttpConfigurers =
                    SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

            for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
                http.apply(configurer);
            }
        }
        //这里调用三个子类各自对应的配置,采用了模板模式
        configure(http);
        return http;
    }

上述httpSecurity的链式调用其实是在构造configurer,一个configurer对应一个过滤器,构造configurer就是为了后面构造过滤器。所有的configurer被添加到集合configurers 中,集合定义在httpSecurity的父类AbstractConfiguredSecurityBuilder,如下:

private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>();


添加 configurer 逻辑如下:

    public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
            throws Exception {
        configurer.addObjectPostProcessor(objectPostProcessor);
        configurer.setBuilder((B) this);
        //将各个过滤器配置添加到 configurers集合中
        add(configurer);
        return configurer;
    }


以上是公共逻辑,分别看三个实现类的实现:

1)AuthorizationServerSecurityConfiguration

protected void configure(HttpSecurity http) throws Exception {
        //构造oauth2的 configurer,用来生成oauth2请求过滤器
        AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
        FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping();
        http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping);
        //这里会初始化子类的配置
        configure(configurer);

        //将configurer添加到httpSecurity过滤器集合中
        http.apply(configurer);
        String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token");
        String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key");
        String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token");
        if (!endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) {
            UserDetailsService userDetailsService = http.getSharedObject(UserDetailsService.class);
            endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService);
        }
        // @formatter:off
        http
            .authorizeRequests()
                .antMatchers(tokenEndpointPath).fullyAuthenticated()
                .antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess())
                .antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess())
        .and()
            .requestMatchers()
                .antMatchers(tokenEndpointPath, tokenKeyPath, checkTokenPath)
        .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
        // @formatter:on
        http.setSharedObject(ClientDetailsService.class, clientDetailsService);
    }

上面一行关键代码configure(configurer);会调用子类的配置,也就是我们自定义的配置,具体如下:

 @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
       //这里主要是开启客户端登录方式
        oauthServer.allowFormAuthenticationForClients();
        //这里添加自定义的用户认证过滤器,如果不配置的话,在TokenEndpoint中的`/oauth/token`方法中还是会进行一次密码认证的(前提是匹配到对应的granter)
        oauthServer.addTokenEndpointAuthenticationFilter(new SecurityTokenEndpointAuthenticationFilter(authenticationManager, oAuth2RequestFactory));
    }

来看一下client 开启的方式,在AuthorizationServerSecurityConfigurer的配置方法configure中:
首先this.allowFormAuthenticationForClients = true;,通过该变量开启,如果开启了allowFormAuthenticationForClients,则进行Client 过滤器的配置,具体代码如下:

    @Override
public void configure(HttpSecurity http) throws Exception {

    frameworkEndpointHandlerMapping();
    //在这里判断该变量
    if (allowFormAuthenticationForClients) {
        //添加过滤器到 httpSecurity 对象中
        clientCredentialsTokenEndpointFilter(http);
    }

    for (Filter filter : tokenEndpointAuthenticationFilters) {
        http.addFilterBefore(filter, BasicAuthenticationFilter.class);
    }

    http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
    if (sslOnly) {
        http.requiresChannel().anyRequest().requiresSecure();
    }

}

跟进上面添加过滤器的方法clientCredentialsTokenEndpointFilter(http);

private ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter(HttpSecurity http) {
        //创建被添加的过滤器,也就是 Client认证过滤器
        ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter = new ClientCredentialsTokenEndpointFilter(
                frameworkEndpointHandlerMapping().getServletPath("/oauth/token"));
        clientCredentialsTokenEndpointFilter
                .setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
                //设置Client认证失败后的处理逻辑,交给oauth2的OAuth2AuthenticationEntryPoint对象,该对象只有认证失败时才会执行。
        OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
        authenticationEntryPoint.setTypeName("Form");
        authenticationEntryPoint.setRealmName(realm);
        clientCredentialsTokenEndpointFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
        clientCredentialsTokenEndpointFilter = postProcess(clientCredentialsTokenEndpointFilter);
        //添加过滤器,这里该过滤器被添加到BasicAuthenticationFilter之前,如果失败就直接返回。          
   http.addFilterBefore(clientCredentialsTokenEndpointFilter,  BasicAuthenticationFilter.class);
        return clientCredentialsTokenEndpointFilter;
    }

以上主要是 构造oauth2的 configurer,用来生成oauth2请求过滤器,最后添加到httpSecurity过滤器集合中。

2)WebSecurityConfig

这个相对简单,就是httpSecurity对象的常见配置,属于spring security的配置内容:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http.requestMatchers().antMatchers(HttpMethod.OPTIONS, "/**")
                .and()
                .authorizeRequests().antMatchers("/oauth/token").permitAll()
                .and()
                .csrf().disable()
                .headers()
                .cacheControl().and()
                .xssProtection().disable()
             .frameOptions().sameOrigin();
        // @formatter:on
    }

上面比较关键的就是 .authorizeRequests().antMatchers(“/oauth/token”).permitAll(),这里是配置 /oauth/token 请求不进行过滤,委托给 oauth2来处理,这点很重要。

3)ResourceServerConfiguration

这个oauth2关于资源的配置,主要配置如下:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        ResourceServerSecurityConfigurer resources = new ResourceServerSecurityConfigurer();
        ResourceServerTokenServices services = resolveTokenServices();
        if (services != null) {
            //配置token服务
            resources.tokenServices(services);
        }
        else {
            if (tokenStore != null) {
                resources.tokenStore(tokenStore);
            }
            else if (endpoints != null) {
                resources.tokenStore(endpoints.getEndpointsConfigurer().getTokenStore());
            }
        }
        if (eventPublisher != null) {
            resources.eventPublisher(eventPublisher);
        }
        for (ResourceServerConfigurer configurer : configurers) {
            //设置resourceId
            configurer.configure(resources);
        }
        http.authenticationProvider(new AnonymousAuthenticationProvider("default"))
        .exceptionHandling()
                .accessDeniedHandler(resources.getAccessDeniedHandler()).and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .csrf().disable();
        http.apply(resources);
        if (endpoints != null) {
            http.requestMatcher(new NotOAuthRequestMatcher(endpoints.oauth2EndpointHandlerMapping()));
        }
        for (ResourceServerConfigurer configurer : configurers) {
            //整合spring security的关键,将授权逻辑委托给spring security
            configurer.configure(http);
        }
        if (configurers.isEmpty()) {
            http.authorizeRequests().anyRequest().authenticated();
        }
    }

上述代码最关键的一行是,configurer.configure(http); 这里是整合spring security的关键,将授权逻辑委托给spring security,具体就是我们自己的整合配置了,上一篇博客已经贴出来了,代码如下:

 @Override
        public void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http.requestMatchers().antMatchers("/**")
                    .and()
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                        public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
                        //这里设置访问权限控制类,和资源查找类,都是spring security中的bean
                            fsi.setAccessDecisionManager(accessDecisionManager());
                            fsi.setSecurityMetadataSource(securityMetadataSource());
                            return fsi;
                        }
                    });
            // @formatter:on
        }


4)总结

上面的源码主要是项目启动时spring security和oauth2配置的加载,可以看出,二者的整合离不开一个重要对象:httpSecurity,spring security和oauth2的配置都是在初始化和构造该对象,然后构造过滤器。
最终httpSecurity对象中的 configurers集合如下:
这里写图片描述

可以看到,oauth2的configurer已经加到了该对象中,每个configurer都对应一个过滤器。

下一篇会对oauth2 和 spring security认证授权源码进行分析。

猜你喜欢

转载自blog.csdn.net/woshilijiuyi/article/details/78964341