天天看点

Spring Security Oauth2 - 源码解析之授权服务器【02】

一、涉及到的类

EnableAuthorizationServer
AuthorizationServerEndpointsConfiguration
AuthorizationServerSecurityConfiguration
AuthorizationServerConfigurerAdapter 
           

二、代码分析

1、授权服务器配置示例代码

我们先从使用代码说起看下授权服务器的几个部件,下面是我们上一节用例中的授权服务器配置代码

@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Autowired
    AuthenticationManager authenticationManager;
    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    @Autowired
    UserDetailsService userDetailsService;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //配置两个客户端,一个用于password认证一个用于client认证
        clients.inMemory()
            .withClient("client_1")
            .resourceIds(DEMO_RESOURCE_ID)
            .authorizedGrantTypes("client_credentials")
            .scopes("select")
            .authorities("oauth2")
            .secret("123456")
            .and()
            .withClient("client_2")
            .resourceIds(DEMO_RESOURCE_ID)
            .authorizedGrantTypes("password", "refresh_token")
            .scopes("select")
            .authorities("oauth2")
            .secret("123456");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
            .tokenStore(new RedisTokenStore(redisConnectionFactory))
            .tokenStore(new InMemoryTokenStore())
            .authenticationManager(authenticationManager)
            .userDetailsService(userDetailsService)
            // 允许 GET、POST 请求获取 token,即访问端点:oauth/token
            .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);

        endpoints.reuseRefreshTokens(true);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        //允许表单认证
        oauthServer.allowFormAuthenticationForClients();
    }

}
           

@EnableAuthorizationServer 主要是启用授权服务器

然后我们继承了 AuthorizationServerConfigurerAdapter 适配器类,然后重写了里面的几个方法

下面我们一个个来看

@EnableAuthorizationServer

我们知道 spring boot 中大量采用了这种@Enabled插拔式设计,通过一个注解,将所需要的组件都给打包整理进来了,屏蔽了对外使用的复杂性

那么我们就来看下,这个注解里面都提供了哪些东西

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AuthorizationServerEndpointsConfiguration.class, AuthorizationServerSecurityConfiguration.class})
public @interface EnableAuthorizationServer {

}
           

先来熟悉一下oauth2 的包结构:

Spring Security Oauth2 - 源码解析之授权服务器【02】

注解通过导入了2个类来完成授权服务所需要的所有配置

AuthorizationServerEndpointsConfiguration

AuthorizationServerSecurityConfiguration

AuthorizationServerEndpointsConfiguration
@Configuration
@Import(TokenKeyEndpointRegistrar.class)
public class AuthorizationServerEndpointsConfiguration {

	private AuthorizationServerEndpointsConfigurer endpoints = new AuthorizationServerEndpointsConfigurer();

	@Autowired
	private ClientDetailsService clientDetailsService;

	@Autowired
	private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();

	@PostConstruct
	public void init() {
		for (AuthorizationServerConfigurer configurer : configurers) {
			try {
				configurer.configure(endpoints);
			} catch (Exception e) {
				throw new IllegalStateException("Cannot configure enpdoints", e);
			}
		}
		endpoints.setClientDetailsService(clientDetailsService);
	}

	@Bean
	public AuthorizationEndpoint authorizationEndpoint() throws Exception {
		AuthorizationEndpoint authorizationEndpoint = new AuthorizationEndpoint();
		FrameworkEndpointHandlerMapping mapping = getEndpointsConfigurer().getFrameworkEndpointHandlerMapping();
		authorizationEndpoint.setUserApprovalPage(extractPath(mapping, "/oauth/confirm_access"));
		authorizationEndpoint.setProviderExceptionHandler(exceptionTranslator());
		authorizationEndpoint.setErrorPage(extractPath(mapping, "/oauth/error"));
		authorizationEndpoint.setTokenGranter(tokenGranter());
		authorizationEndpoint.setClientDetailsService(clientDetailsService);
		authorizationEndpoint.setAuthorizationCodeServices(authorizationCodeServices());
		authorizationEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
		authorizationEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
		authorizationEndpoint.setUserApprovalHandler(userApprovalHandler());
		return authorizationEndpoint;
	}

	@Bean
	public TokenEndpoint tokenEndpoint() throws Exception {
		TokenEndpoint tokenEndpoint = new TokenEndpoint();
		tokenEndpoint.setClientDetailsService(clientDetailsService);
		tokenEndpoint.setProviderExceptionHandler(exceptionTranslator());
		tokenEndpoint.setTokenGranter(tokenGranter());
		tokenEndpoint.setOAuth2RequestFactory(oauth2RequestFactory());
		tokenEndpoint.setOAuth2RequestValidator(oauth2RequestValidator());
		tokenEndpoint.setAllowedRequestMethods(allowedTokenEndpointRequestMethods());
		return tokenEndpoint;
	}

	@Bean
	public CheckTokenEndpoint checkTokenEndpoint() {
		CheckTokenEndpoint endpoint = new CheckTokenEndpoint(getEndpointsConfigurer().getResourceServerTokenServices());
		endpoint.setAccessTokenConverter(getEndpointsConfigurer().getAccessTokenConverter());
		endpoint.setExceptionTranslator(exceptionTranslator());
		return endpoint;
	}

    .....

	
	@Bean
	public AuthorizationServerTokenServices defaultAuthorizationServerTokenServices() {
		return endpoints.getDefaultAuthorizationServerTokenServices();
	}

	.....

	@Component
	protected static class TokenKeyEndpointRegistrar implements BeanDefinitionRegistryPostProcessor {

		private BeanDefinitionRegistry registry;

		@Override
		public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
			String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory,
					JwtAccessTokenConverter.class, false, false);
			if (names.length > 0) {
				BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(TokenKeyEndpoint.class);
				builder.addConstructorArgReference(names[0]);
				registry.registerBeanDefinition(TokenKeyEndpoint.class.getName(), builder.getBeanDefinition());
			}
		}

		@Override
		public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
			this.registry = registry;
		}

	}

}
           
从这个类中,主要加载了哪些默认配置呢,核心的我已经放在上面了,其他的省略号代替
  • AuthorizationEndpoint 授权端点,这个很重要,这是授权的请求入口都在里面
  • TokenEndpoint 主要是生成token逻辑都在里面
  • CheckTokenEndpoint 校验token的逻辑
  • AuthorizationServerTokenServices 授权服务token服务,这个很重要,TokenEndpoint 为接入层,那么这个就是服务层了,面向数据
  • TokenKeyEndpointRegistrar token key 生成相关的一些组件聚合
细节暂时先不展开了,目前先看到这里,大致有个框架认识,知道各个组件是在哪里被组织起来的就可以了
AuthorizationServerSecurityConfiguration
@Configuration
@Order(0)
@Import({ ClientDetailsServiceConfiguration.class, AuthorizationServerEndpointsConfiguration.class })
public class AuthorizationServerSecurityConfiguration extends WebSecurityConfigurerAdapter {

	@Autowired
	private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();

	@Autowired
	private ClientDetailsService clientDetailsService;

	@Autowired
	private AuthorizationServerEndpointsConfiguration endpoints;

	@Autowired
	public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception {
		for (AuthorizationServerConfigurer configurer : configurers) {
			configurer.configure(clientDetails);
		}
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		// Over-riding to make sure this.disableLocalConfigureAuthenticationBldr = false
		// This will ensure that when this configurer builds the AuthenticationManager it will not attempt
		// to find another 'Global' AuthenticationManager in the ApplicationContext (if available),
		// and set that as the parent of this 'Local' AuthenticationManager.
		// This AuthenticationManager should only be wired up with an AuthenticationProvider
		// composed of the ClientDetailsService (wired in this configuration) for authenticating 'clients' only.
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
		FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping();
		http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping);
		configure(configurer);
		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);
	}

	protected void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
		for (AuthorizationServerConfigurer configurer : configurers) {
			configurer.configure(oauthServer);
		}
	}

}
           
这里面做的事情比较简单,主要就是打辅助。因为oauth2框架是基于 spring security 的,上面的逻辑主要是对用户请求进行一些权限设置。设置的都是AuthorizationServerEndpointsConfiguration 中配置的一些框架内置的授权端点的请求限制,像/oauth/token就是获取token的端点

2、AuthorizationServerConfigurerAdapter

public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer {

	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {		// 1
	}

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {				// 2
	}

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {		// 3
	}

}
           

《1处》 配置AuthorizationServer安全认证的相关信息,创建ClientCredentialsTokenEndpointFilter核心过滤器

《2处》 配置OAuth2的客户端相关信息。client 端信息配置,可以是内存,也可以是数据库等,我们上面的例子中采用的InMemoryClientDetailsServiceBuilder 内存为数据源的方式存放client信息。

《3处》 配置AuthorizationServerEndpointsConfigurer众多相关类,包括配置身份认证器,配置认证方式,TokenStore,TokenGranter,OAuth2RequestFactory。主要是功能组件的指定,可以系统,也可以自定义

然而 AuthorizationServerConfigurerAdapter 本质是对 AuthorizationServerConfigurer 接口的实现。通过覆盖上面三个方法,进行自定义配置注入

《1处》方法的调用位置 AuthorizationServerSecurityConfiguration#configure(AuthorizationServerSecurityConfigurer)

protected void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    for (AuthorizationServerConfigurer configurer : configurers) {
        configurer.configure(oauthServer);
    }
}
           

《2处》方法的调用位置 AuthorizationServerSecurityConfiguration#configure(ClientDetailsServiceConfigurer)

@Autowired
public void configure(ClientDetailsServiceConfigurer clientDetails) throws Exception {
    for (AuthorizationServerConfigurer configurer : configurers) {
        configurer.configure(clientDetails);
    }
}
           

《3处》方法的调用位置 AuthorizationServerEndpointsConfiguration 构造器调用后进行的init初始化操作

@PostConstruct
public void init() {
    for (AuthorizationServerConfigurer configurer : configurers) {
        try {
            configurer.configure(endpoints);
        } catch (Exception e) {
            throw new IllegalStateException("Cannot configure enpdoints", e);
        }
    }
    endpoints.setClientDetailsService(clientDetailsService);
}
           

三、总结

至此,整个授权架构模式,已经很清楚的勾画出来了,后面我们对其中的几个核心组件进行讲解

参考:https://www.cnkirito.moe/Spring-Security-OAuth2-2/

继续阅读