您的位置:首页 > 编程语言 > Java开发

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

2018-01-03 18:50 731 查看
继上一篇《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认证授权源码进行分析。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息