源码解读-oauther2-客户端信息配置

发现问题

在配置客户端信息的时候,打算采用configure方法配置,代码如下:

        //自定义模式
        JdbcClientDetailsServiceBuilder jdbcClientDetailsServiceBuilder = new JdbcClientDetailsServiceBuilder().dataSource(dataSource).passwordEncoder(passwordEncoder);
        clients.configure(jdbcClientDetailsServiceBuilder);

代码测试发现会报错,Cannot build client services (maybe use inMemory() or jdbc()).,然后查看父类,发现这个是来自父类。

问题查看:

通过jdbc类找到父类ClientDetailsServiceBuilder,然后找到ClientDetailsServiceConfiguration和ClientDetailsServiceConfigurer

下面代码是ClientDetailsServiceConfiguration
默认配置了ClientDetailsServiceBuilder,也就是jdbc的父类
这里最重要的就是客户业务服务bean,INTERFACES代表要使用JDK的动态代理来创建代理对象。
所以上面代码并没有影响到这里的对象实例。

@Configuration
public class ClientDetailsServiceConfiguration {

	@SuppressWarnings("rawtypes")
	private ClientDetailsServiceConfigurer configurer = new ClientDetailsServiceConfigurer(new ClientDetailsServiceBuilder());
	
	@Bean
	public ClientDetailsServiceConfigurer clientDetailsServiceConfigurer() {
		return configurer;
	}

	@Bean
	@Lazy
	@Scope(proxyMode=ScopedProxyMode.INTERFACES)
	public ClientDetailsService clientDetailsService() throws Exception {
		return configurer.and().build();
	}

}

下面这个ClientDetailsServiceConfigurer类,可以发现setBuilder和this.and()就是上面动态配置中使用到的方法。
此类持有ClientDetailsServiceBuilder ,ClientDetailsServiceBuilder的子类负责通过ClientBuilder创建Client

public class ClientDetailsServiceConfigurer extends
		SecurityConfigurerAdapter<ClientDetailsService, ClientDetailsServiceBuilder<?>> {

	public ClientDetailsServiceConfigurer(ClientDetailsServiceBuilder<?> builder) {
		setBuilder(builder);
	}

	public ClientDetailsServiceBuilder<?> withClientDetails(ClientDetailsService clientDetailsService) throws Exception {
		setBuilder(getBuilder().clients(clientDetailsService));
		return this.and();
	}

	public InMemoryClientDetailsServiceBuilder inMemory() throws Exception {
		InMemoryClientDetailsServiceBuilder next = getBuilder().inMemory();
		setBuilder(next);
		return next;
	}
	public JdbcClientDetailsServiceBuilder jdbc(DataSource dataSource) throws Exception {
		JdbcClientDetailsServiceBuilder next = getBuilder().jdbc().dataSource(dataSource);
		setBuilder(next);
		return next;
	}
	
	@Override
	public void init(ClientDetailsServiceBuilder<?> builder) throws Exception {
	}

	@Override
	public void configure(ClientDetailsServiceBuilder<?> builder) throws Exception {
	}

}

不管是InMemoryClientDetailsServiceBuilder 还是JdbcClientDetailsServiceBuilder都继承ClientDetailsServiceBuilder,ClientDetailsServiceBuilder持有private List clientBuilders = new ArrayList(); 保存了所有客户端信息配置。

然后看看ClientBuilder,是一个内部类,路径是org.springframework.security.oauth2.config.annotation.builders。

回到一开始说的ClientDetailsServiceConfiguration,生成的客户端业务是configurer.and().build()。点开and方法

	public B and() {
		return getBuilder();
	}

继续看getBuilder,这里是拿到父类中的securityBuilder

	protected final B getBuilder() {
		if (securityBuilder == null) {
			throw new IllegalStateException("securityBuilder cannot be null");
		}
		return securityBuilder;
	}

父类的securityBuilder是什么呢?是一个继承SecurityBuilder的泛型

public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>>
		implements SecurityConfigurer<O, B> {
	private B securityBuilder;

再看看客户端client的几个方法

	public ClientDetailsServiceConfigurer(ClientDetailsServiceBuilder<?> builder) {
		setBuilder(builder);
	}

	public ClientDetailsServiceBuilder<?> withClientDetails(ClientDetailsService clientDetailsService) throws Exception {
		setBuilder(getBuilder().clients(clientDetailsService));
		return this.and();
	}

	public InMemoryClientDetailsServiceBuilder inMemory() throws Exception {
		InMemoryClientDetailsServiceBuilder next = getBuilder().inMemory();
		setBuilder(next);
		return next;
	}
	public JdbcClientDetailsServiceBuilder jdbc(DataSource dataSource) throws Exception {
		JdbcClientDetailsServiceBuilder next = getBuilder().jdbc().dataSource(dataSource);
		setBuilder(next);
		return next;
	}

可以发现都存在setBuilder方法,这是一个父类SecurityConfigurerAdapter的方法,大概可以猜到点了,就是替换数据。

建造者模式梳理:
configuration配置生成一个配置类ClientDetailsServiceConfigurer,配置类继承了适配器SecurityConfigurerAdapter抽象类,适配器中包括了securityBuilder,这是来父类接口SecurityConfigurer中的SecurityBuilder(接口只是规定方法),初始化的时候存入ClientDetailsServiceBuilder。

public class ClientDetailsServiceConfigurer extends
    SecurityConfigurerAdapter<ClientDetailsService, ClientDetailsServiceBuilder<?>>
public class ClientDetailsServiceBuilder<B extends ClientDetailsServiceBuilder<B>> extends
		SecurityConfigurerAdapter<ClientDetailsService, B> implements SecurityBuilder<ClientDetailsService> 

这里为什么生成ClientDetailsServiceConfigurer的bean,而不是直接用适配器呢,因为这个类要进行配置替换适配器的内容,适配器只做适配器该做的事情。

下面是适配器,private B securityBuilder就是适配器的私有内容,这个是一个builder,用于生成O,这个用于生成O是什么时候用呢,就是build方法的时候使用,build方法也就是生成bean的地方进行使用的。那么就很清楚了,先生成一个配置实例,配置实例可以实现配置类中的B,而这个B就是为了生成业务对象,所以最终获取这个B,然后生成业务对象bean就完成了最终的功能。

public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>>
		implements SecurityConfigurer<O, B> {
		private B securityBuilder;

然后回到ClientDetailsServiceBuilder,是实现SecurityBuilder。
看下面的方法,不就是跟配置类的方法一一对应的吗?客户端有内存模式,jdbc模式,业务service模式,看下面方法就很清楚了,最终都是生成了builder,然后在配置类中去替换B,而生成业务方法也在下面build方法,

public class ClientDetailsServiceBuilder<B extends ClientDetailsServiceBuilder<B>> extends
		SecurityConfigurerAdapter<ClientDetailsService, B> implements SecurityBuilder<ClientDetailsService> {

	private List<ClientBuilder> clientBuilders = new ArrayList<ClientBuilder>();

	public InMemoryClientDetailsServiceBuilder inMemory() throws Exception {
		return new InMemoryClientDetailsServiceBuilder();
	}

	public JdbcClientDetailsServiceBuilder jdbc() throws Exception {
		return new JdbcClientDetailsServiceBuilder();
	}

	@SuppressWarnings("rawtypes")
	public ClientDetailsServiceBuilder<?> clients(final ClientDetailsService clientDetailsService) throws Exception {
		return new ClientDetailsServiceBuilder() {
			@Override
			public ClientDetailsService build() throws Exception {
				return clientDetailsService;
			}
		};
	}

	public ClientBuilder withClient(String clientId) {
		ClientBuilder clientBuilder = new ClientBuilder(clientId);
		this.clientBuilders.add(clientBuilder);
		return clientBuilder;
	}

	@Override
	public ClientDetailsService build() throws Exception {
		for (ClientBuilder clientDetailsBldr : clientBuilders) {
			addClient(clientDetailsBldr.clientId, clientDetailsBldr.build());
		}
		return performBuild();
	}

从上面代码可以看出,对于入参是业务service,就直接重写了build方法,然后将业务service直接返回即可。
而对于内存模式和jdbc模式,先addClient,然后performBuild,而这个客户的信息是在withClient的时候加入的。
对于内存模式来说,通过withClient加入客户信息,然后addClient方法加入到父类builder内存,performBuild的时候保存到了内存的builder中。
对于jdbc来说,withClient和addClient是在jdbc的基础上额外加入的客户端,也就是说如果是想在数据库的基础上固定一些客户端,可以采用withClient方法加入,然后配置jdbc。

总结:

建造者模式的组成:适配器,适配器配置,父类生成器
地基:生成适配器配置类(包含了生成对应子生成器方法和替换适配器的方法)
建造:生成子生成器并替换原来的生成器,最终替换了生成的业务service

回到问题,为什么configure没有效果呢,因为这个方法是空的,并没有影响到原来的生成器,所以需要重写方法。但是ClientDetailsServiceConfiguration配置的是ClientDetailsServiceConfigurer,所以就需要剔除这个配置类,然后手写一个替换上去。个人角色这样不妥,剔除后就不会加载到这个配置,如果oauther升级后实现了这个方法,那就体会不到升级的好处了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值