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

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

微信公众号:吉姆餐厅ak
学习更多源码知识,欢迎关注。
2019112310080\_1.png

继上一篇《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的配置类一并初始化,然后针对配置类进行加载对应的配置。

接下来重点说一下配置类。
首先这里采用了模板模式,抽象了部分相同的逻辑,也就是构造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的配置都是在初始化和构造该对象,然后构造过滤器。

友链:探果网


来源:http://ddrv.cn/a/88268

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » SpringSecurity | spring security oauth2.0 配置源码分析(二)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏