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

微信公众号:吉姆餐厅ak
学习更多源码知识,欢迎关注。
《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的配置类一并初始化,然后针对配置类进行加载对应的配置。

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

友链:探果网

    原文作者:Spring Cloud
    原文地址: https://blog.csdn.net/woshilijiuyi/article/details/78964341
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞