注意:本小节内容作为选学内容,但是难度比前两章的源码部分简单得多。
最后我们再来聊一下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
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对象。