springboot情操陶冶-web配置(九)

承接前文springboot情操陶冶-web配置(八),本文在前文的基础上深刻了解下WebSecurity类的运做逻辑html

WebSecurityConfigurerAdapter

在剖析WebSecurity的工做逻辑以前,先预热下springboot security推荐复写的抽象类WebSecurityConfigurerAdapter,观察下其是如何被实例化的,方便后续的深刻理解java


1.ApplicationContext环境设置web

@Autowired
	public void setApplicationContext(ApplicationContext context) {
		this.context = context;

		ObjectPostProcessor<Object> objectPostProcessor = context.getBean(ObjectPostProcessor.class);
		LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(context);
		// 
		authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder);
		localConfigureAuthenticationBldr = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder) {
			@Override
			public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
				authenticationBuilder.eraseCredentials(eraseCredentials);
				return super.eraseCredentials(eraseCredentials);
			}

		};
	}

同前文的认证管理器建立差很少,但这里细心的能够看到其内部建立了两个如出一辙的认证管理器对象,目的应该是为了引用AuthenticationConfiguration对象中的认证管理器做准备,下文会说起spring


2.引入前文所提的AuthenticationConfiguration对象缓存

@Autowired
	public void setAuthenticationConfiguration(
			AuthenticationConfiguration authenticationConfiguration) {
		this.authenticationConfiguration = authenticationConfiguration;
	}

目的是间接调用此类获取对应的认证管理器AuthenticationManager对象,具体的读者可自行阅读安全


3.共享变量集合,security板块引入了共享变量,目的就跟缓存同样springboot

private Map<Class<? extends Object>, Object> createSharedObjects() {
		Map<Class<? extends Object>, Object> sharedObjects = new HashMap<Class<? extends Object>, Object>();
		sharedObjects.putAll(localConfigureAuthenticationBldr.getSharedObjects());
		sharedObjects.put(UserDetailsService.class, userDetailsService());
		sharedObjects.put(ApplicationContext.class, context);
		sharedObjects.put(ContentNegotiationStrategy.class, contentNegotiationStrategy);
		sharedObjects.put(AuthenticationTrustResolver.class, trustResolver);
		return sharedObjects;
	}

OK,差很少就这样,开始咱们的主角之旅把session

WebSecurityConfiguration

根据前文可知,WebSecurity对象的建立与运做都是经过WebSecurityConfiguration来的,笔者在此处再做下简单的概括 1.实例化WebSecurity对象并注册至ApplicationContext上下文对象中 2.获取ApplicationContext对象上注册的类型为WebSecurityConfigurer的接口集合,存放至WebSecurity的父类AbstractConfiguredSecurityBuilder的内部属性*configurers(hashmap)中 3.运行WebSecurity的build()*方法建立Filter过滤链app

基于上述的结论咱们再着重看下第三点的建立过滤链是如何完成的,本文侧重点也在于此ide

WebSecurity#build()

*build()*方法是由其父类AbstractSecurityBuilder来完成的,代码很简单

public final O build() throws Exception {
		// 确保只会被build一次
		if (this.building.compareAndSet(false, true)) {
			this.object = doBuild();
			return this.object;
		}
		throw new AlreadyBuiltException("This object has already been built");
	}

接着跟踪*doBuild()*模板方法,发现是由其子类抽象类AbstractConfiguredSecurityBuilder来操做的

@Override
	protected final O doBuild() throws Exception {
		synchronized (configurers) {
			buildState = BuildState.INITIALIZING;

			// init
			beforeInit();
			init();

			buildState = BuildState.CONFIGURING;

			// configure
			beforeConfigure();
			configure();

			buildState = BuildState.BUILDING;

			// build
			O result = performBuild();

			buildState = BuildState.BUILT;

			return result;
		}
	}

大体能够分为三步走,下面笔者就按照上述的三步一一分开剖析

初始化阶段

分为*beforeInit()init()*两个操做

beforeInit()

beforeInit()初始化前的操做,默认是空的,可供用户去复写

init()

init()初始化方法比较有意思了,看下其源码

private void init() throws Exception {
		// WebSecurity内的configurer是WebSecurityConfigurer类型的
		Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

		// 遍历调用公用的init()方法,关注此处的this指代WebSecurity对象
		for (SecurityConfigurer<O, B> configurer : configurers) {
			configurer.init((B) this);
		}

		// 候补
		for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
			configurer.init((B) this);
		}
	}

好,笔者来看下*configurer#init()*方法,此处只针对WebSecurityConfigurer接口的初始化。即便用户没有去实现该接口,springboot也会默认有个类去实现

@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
public class SpringBootWebSecurityConfiguration {

	@Configuration
	@Order(SecurityProperties.BASIC_AUTH_ORDER)
	static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {

	}

}

很明显,就是继承WebSecurityConfigurerAdapter类来实现的,那咱们看下其*init()*方法的操做

public void init(final WebSecurity web) throws Exception {
		// important
		final HttpSecurity http = getHttp();
		// configure interceptor?
		web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
			public void run() {
				FilterSecurityInterceptor securityInterceptor = http
						.getSharedObject(FilterSecurityInterceptor.class);
				web.securityInterceptor(securityInterceptor);
			}
		});
	}

上述的代码比较关键,笔者按两个小步骤讲述一下 1.建立HttpSecurity对象,贴出源码仔细分析下

protected final HttpSecurity getHttp() throws Exception {
		if (http != null) {
			return http;
		}

		// 配置事件发行器
		DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
				.postProcess(new DefaultAuthenticationEventPublisher());
		localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

		// 获取父级认证管理器,涉及configure(AuthenticationManagerBuilder auth)方法复写
		AuthenticationManager authenticationManager = authenticationManager();
		authenticationBuilder.parentAuthenticationManager(authenticationManager);
		Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();

		// 建立HttpSecurity对象
		http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
				sharedObjects);
		// 是否屏蔽默认配置,默认为false
		if (!disableDefaults) {
			// @formatter:off
			http
				.csrf().and()
				.addFilter(new WebAsyncManagerIntegrationFilter())
				.exceptionHandling().and()
				.headers().and()
				.sessionManagement().and()
				.securityContext().and()
				.requestCache().and()
				.anonymous().and()
				.servletApi().and()
				.apply(new DefaultLoginPageConfigurer<>()).and()
				.logout();
			// @formatter:on
			ClassLoader classLoader = this.context.getClassLoader();
			// 加载spring.factories中以AbstractHttpConfigurer做为Key的类型集合
			List<AbstractHttpConfigurer> defaultHttpConfigurers =
					SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

			for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
				http.apply(configurer);
			}
		}
		// 供用户自定义配置HTTP安全配置,默认支持表单和Basic方式提交认证信息
		configure(http);
		return http;
	}

代码信息过多,不一一分析了,springboot推荐用户复写如下两个方法

  • configure(AuthenticationManagerBuilder auth) 配置认证管理器,用户信息读取方式、加密方式都可经过此方法配置
  • configure(HttpSecurity http) 配置http服务,路径拦截、csrf保护等等都可经过此方法配置

2.将HttpSecurity放入WebSecurity中,细看发现HttpSecurity是用来建立FilterChain过滤链的。并尝试塞入一个FilterSecurityInterceptor拦截器,默认通常都是会配置的。

配置阶段

分为*beforeConfigure()configure()*阶段

beforeConfigure()

配置前的操做,供用户去复写

configure()

遍历其下的全部的WebSecurityConfigurerAdapter的实现类,统一调用*configure(WebSecurity web)*配置。很明显,用户也能够复写方法来配置,好比对HttpSecurity默认的配置不满意,也能够经过此类来复写之;屏蔽一些URL的访问等等。

建立阶段

真正的建立Filter对象是由*performBuild()*方法执行的,别看源码很长,其实就一个意思,经过HttpSecurity对象来建立Filter过滤链

@Override
	protected Filter performBuild() throws Exception {
		Assert.state(
				!securityFilterChainBuilders.isEmpty(),
				"At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke "
						+ WebSecurity.class.getSimpleName()
						+ ".addSecurityFilterChainBuilder directly");
		int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
				chainSize);
		// 用户可经过调用websecurity.ignoring()方法来屏蔽一些访问路径
		for (RequestMatcher ignoredRequest : ignoredRequests) {
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			// HttpSecurity#build()会被执行,执行逻辑就跟本文的WebSecurity如出一辙
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		// 防火墙配置
		if (httpFirewall != null) {
			filterChainProxy.setFirewall(httpFirewall);
		}
		filterChainProxy.afterPropertiesSet();

		Filter result = filterChainProxy;
		
		postBuildAction.run();
		return result;
	}

具体的用户可自行去阅读

小结

本文主要讲述了WebSecurity与HttpSecurity之间的关系以及如何被建立,其实都是同样的,它们都是AbstractConfiguredSecurityBuilder的复写类,核心都是会执行其中的*build()*模板方法来建立过滤链。笔者或者读者只须要复写其中的几个重要方法即可实现简单的安全配置。但愿本文对你们有用

相关文章
相关标签/搜索