SpringSecurity原理
注意:本小节内容作为选学内容,但是难度比前两章的源码部分简单得多。
最后我们再来聊一下SpringSecurity的实现原理,它本质上是依靠N个Filter实现的,也就是一个完整的过滤链(注意这里是过滤器,不是拦截器)
我们就从AbstractSecurityWebApplicationInitializer
开始下手,我们来看看它配置了什么:
//此方法会在启动时被调用
public final void onStartup(ServletContext servletContext) {
this.beforeSpringSecurityFilterChain(servletContext);
if (this.configurationClasses != null) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(this.configurationClasses);
servletContext.addListener(new ContextLoaderListener(rootAppContext));
}
if (this.enableHttpSessionEventPublisher()) {
servletContext.addListener("org.springframework.security.web.session.HttpSessionEventPublisher");
}
servletContext.setSessionTrackingModes(this.getSessionTrackingModes());
//重点在这里,这里插入了关键的FilterChain
this.insertSpringSecurityFilterChain(servletContext);
this.afterSpringSecurityFilterChain(servletContext);
}
private void insertSpringSecurityFilterChain(ServletContext servletContext) {
String filterName = "springSecurityFilterChain";
//创建了一个DelegatingFilterProxy对象,它本质上也是一个Filter
DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(filterName);
String contextAttribute = this.getWebApplicationContextAttribute();
if (contextAttribute != null) {
springSecurityFilterChain.setContextAttribute(contextAttribute);
}
//通过ServletContext注册DelegatingFilterProxy这个Filter
this.registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}
我们接着来看看,DelegatingFilterProxy在做什么:
//这个是初始化方法,它由GenericFilterBean(父类)定义,在afterPropertiesSet方法中被调用
protected void initFilterBean() throws ServletException {
synchronized(this.delegateMonitor) {
if (this.delegate == null) {
if (this.targetBeanName == null) {
this.targetBeanName = this.getFilterName();
}
WebApplicationContext wac = this.findWebApplicationContext();
if (wac != null) {
//耐心点,套娃很正常
this.delegate = this.initDelegate(wac);
}
}
}
}
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = this.getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
//这里通过WebApplicationContext获取了一个Bean
Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
if (this.isTargetFilterLifecycle()) {
delegate.init(this.getFilterConfig());
}
//返回Filter
return delegate;
}
这里我们需要添加一个断点来查看到底获取到了什么Bean。
通过断点调试,我们发现这里放回的对象是一个FilterChainProxy类型的,并且调用了它的初始化方法,但是FilterChainProxy类中并没有重写init
方法或是initFilterBean
方法。
我们倒回去看,当Filter返回之后,DelegatingFilterProxy
的一个成员变量delegate
被赋值为得到的Filter,也就是FilterChainProxy对象,接着我们来看看,DelegatingFilterProxy
是如何执行doFilter方法的。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
//非正常情况,这里省略...
}
//这里才是真正的调用,别忘了delegateToUse就是初始化的FilterChainProxy对象
this.invokeDelegate(delegateToUse, request, response, filterChain);
}
protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//最后实际上调用的是FilterChainProxy的doFilter方法
delegate.doFilter(request, response, filterChain);
}
所以我们接着来看,FilterChainProxy
的doFilter方法又在干什么:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (!clearContext) {
//真正的过滤在这里执行
this.doFilterInternal(request, response, chain);
} else {
//...
}
}
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
//这里获取了一个Filter列表,实际上SpringSecurity就是由N个过滤器实现的,这里获取的都是SpringSecurity提供的过滤器
//但是请注意,经过我们之前的分析,实际上真正注册的Filter只有DelegatingFilterProxy
//而这里的Filter列表中的所有Filter并没有被注册,而是在这里进行内部调用
List<Filter> filters = this.getFilters((HttpServletRequest)firewallRequest);
//只要Filter列表不是空,就依次执行内置的Filter
if (filters != null && filters.size() != 0) {
if (logger.isDebugEnabled()) {
logger.debug(LogMessage.of(() -> {
return "Securing " + requestLine(firewallRequest);
}));
}
//这里创建一个虚拟的过滤链,过滤流程是由SpringSecurity自己实现的
FilterChainProxy.VirtualFilterChain virtualFilterChain = new FilterChainProxy.VirtualFilterChain(firewallRequest, chain, filters);
//调用虚拟过滤链的doFilter
virtualFilterChain.doFilter(firewallRequest, firewallResponse);
} else {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.of(() -> {
return "No security for " + requestLine(firewallRequest);
}));
}
firewallRequest.reset();
chain.doFilter(firewallRequest, firewallResponse);
}
}
我们来看一下虚拟过滤链的doFilter是怎么处理的:
//看似没有任何循环,实际上就是一个循环,是一个递归调用
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
//判断是否已经通过全部的内置过滤器,定位是否等于当前大小
if (this.currentPosition == this.size) {
if (FilterChainProxy.logger.isDebugEnabled()) {
FilterChainProxy.logger.debug(LogMessage.of(() -> {
return "Secured " + FilterChainProxy.requestLine(this.firewalledRequest);
}));
}
this.firewalledRequest.reset();
//所有的内置过滤器已经完成,按照正常流程走DelegatingFilterProxy的下一个Filter
//也就是说这里之后就与DelegatingFilterProxy没有任何关系了,该走其他过滤器就走其他地方配置的过滤器,SpringSecurity的过滤操作已经结束
this.originalChain.doFilter(request, response);
} else {
//定位自增
++this.currentPosition;
//获取当前定位的Filter
Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
if (FilterChainProxy.logger.isTraceEnabled()) {
FilterChainProxy.logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(), this.currentPosition, this.size));
}
//执行内部过滤器的doFilter方法,传入当前对象本身作为Filter,执行如果成功,那么一定会再次调用当前对象的doFilter方法
//可能最不理解的就是这里,执行的难道不是内部其他Filter的doFilter方法吗,怎么会让当前对象的doFilter方法递归调用呢?
//没关系,了解了其中一个内部过滤器就明白了
nextFilter.doFilter(request, response, this);
}
}
因此,我们差不多已经了解了整个SpringSecurity的实现机制了,那么我们来看几个内部的过滤器分别在做什么。
比如用于处理登陆的过滤器UsernamePasswordAuthenticationFilter
,它继承自AbstractAuthenticationProcessingFilter
,我们来看看它是怎么进行过滤的:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
this.doFilter((HttpServletRequest)request, (HttpServletResponse)response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//如果不是登陆请求,那么根本不会理这个请求
if (!this.requiresAuthentication(request, response)) {
//直接调用传入的FilterChain的doFilter方法
//而这里传入的正好是VirtualFilterChain对象
//这下知道为什么上面说是递归了吧
chain.doFilter(request, response);
} else {
//如果是登陆请求,那么会执行登陆请求的相关逻辑,注意执行过程中出现任何问题都会抛出异常
//比如用户名和密码错误,我们之前也已经测试过了,会得到一个BadCredentialsException
try {
//进行认证
Authentication authenticationResult = this.attemptAuthentication(request, response);
if (authenticationResult == null) {
return;
}
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
//如果一路绿灯,没有报错,那么验证成功,执行successfulAuthentication
this.successfulAuthentication(request, response, chain, authenticationResult);
} catch (InternalAuthenticationServiceException var5) {
this.logger.error("An internal error occurred while trying to authenticate the user.", var5);
//验证失败,会执行unsuccessfulAuthentication
this.unsuccessfulAuthentication(request, response, var5);
} catch (AuthenticationException var6) {
this.unsuccessfulAuthentication(request, response, var6);
}
}
}
那么我们来看看successfulAuthentication和unsuccessfulAuthentication分别做了什么:
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
//向SecurityContextHolder添加认证信息,我们可以通过SecurityContextHolder对象获取当前登陆的用户
SecurityContextHolder.getContext().setAuthentication(authResult);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
}
//记住我实现
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
//调用默认的或是我们自己定义的AuthenticationSuccessHandler的onAuthenticationSuccess方法
//这个根据我们配置文件决定
//到这里其实页面就已经直接跳转了
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
//登陆失败会直接清理掉SecurityContextHolder中的认证信息
SecurityContextHolder.clearContext();
this.logger.trace("Failed to process authentication request", failed);
this.logger.trace("Cleared SecurityContextHolder");
this.logger.trace("Handling authentication failure");
//登陆失败的记住我处理
this.rememberMeServices.loginFail(request, response);
//同上,调用默认或是我们自己定义的AuthenticationFailureHandler
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
了解了整个用户验证实现流程,其实其它的过滤器是如何实现的也就很容易联想到了,SpringSecurity的过滤器从某种意义上来说,更像是一个处理业务的Servlet,它做的事情不像是拦截,更像是完成自己对应的职责,只不过是使用了过滤器机制进行实现罢了。
SecurityContextPersistenceFilter也是内置的Filter,可以尝试阅读一下其源码,了解整个SecurityContextHolder的运作原理,这里先说一下大致流程,各位可以依照整个流程按照源码进行推导:
当过滤器链执行到SecurityContextPersistenceFilter时,它会从HttpSession中把SecurityContext对象取出来(是存在Session中的,跟随会话的消失而消失),然后放入SecurityContextHolder对象中。请求结束后,再把SecurityContext存入HttpSession中,并清除SecurityContextHolder内的SecurityContext对象。