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());//重点在这里,这里插入了关键的FilterChainthis.insertSpringSecurityFilterChain(servletContext);this.afterSpringSecurityFilterChain(servletContext);
}
private void insertSpringSecurityFilterChain(ServletContext servletContext) {String filterName = "springSecurityFilterChain";//创建了一个DelegatingFilterProxy对象,它本质上也是一个FilterDelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(filterName);String contextAttribute = this.getWebApplicationContextAttribute();if (contextAttribute != null) {springSecurityFilterChain.setContextAttribute(contextAttribute);}//通过ServletContext注册DelegatingFilterProxy这个Filterthis.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获取了一个BeanFilter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);if (this.isTargetFilterLifecycle()) {delegate.init(this.getFilterConfig());}//返回Filterreturn 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列表不是空,就依次执行内置的Filterif (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);//调用虚拟过滤链的doFiltervirtualFilterChain.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;//获取当前定位的FilterFilter 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 {//如果是登陆请求,那么会执行登陆请求的相关逻辑,注意执行过程中出现任何问题都会抛出异常//比如用户名和密码错误,我们之前也已经测试过了,会得到一个BadCredentialsExceptiontry {//进行认证Authentication authenticationResult = this.attemptAuthentication(request, response);if (authenticationResult == null) {return;}this.sessionStrategy.onAuthentication(authenticationResult, request, response);if (this.continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}//如果一路绿灯,没有报错,那么验证成功,执行successfulAuthenticationthis.successfulAuthentication(request, response, chain, authenticationResult);} catch (InternalAuthenticationServiceException var5) {this.logger.error("An internal error occurred while trying to authenticate the user.", var5);//验证失败,会执行unsuccessfulAuthenticationthis.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);//同上,调用默认或是我们自己定义的AuthenticationFailureHandlerthis.failureHandler.onAuthenticationFailure(request, response, failed);
}

了解了整个用户验证实现流程,其实其它的过滤器是如何实现的也就很容易联想到了,SpringSecurity的过滤器从某种意义上来说,更像是一个处理业务的Servlet,它做的事情不像是拦截,更像是完成自己对应的职责,只不过是使用了过滤器机制进行实现罢了。

SecurityContextPersistenceFilter也是内置的Filter,可以尝试阅读一下其源码,了解整个SecurityContextHolder的运作原理,这里先说一下大致流程,各位可以依照整个流程按照源码进行推导:

当过滤器链执行到SecurityContextPersistenceFilter时,它会从HttpSession中把SecurityContext对象取出来(是存在Session中的,跟随会话的消失而消失),然后放入SecurityContextHolder对象中。请求结束后,再把SecurityContext存入HttpSession中,并清除SecurityContextHolder内的SecurityContext对象。

SpringSecurity原理:探究SpringSecurity运作流程相关推荐

  1. SpringSecurity原理剖析及其实战(三)

    SpringSecurity原理剖析及其实战(三) 1.自定义登录页面 2.过滤器链模式与责任链模式 3.Security Session 4.RememberMe实现 5.退出登录 6.CSRF 1 ...

  2. java lock的原理,Java中Lock原理探究

    在对于lock锁的使用上,很多人只是掌握了最基础的方法,但是对实现的过程不是很清楚.这里我们对lock锁功能的实现进行分析,以ReentrantLock为例,分析它的锁类型,并对相关的调用方法进行展示 ...

  3. KVM 虚拟化原理探究--启动过程及各部分虚拟化原理

    KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...

  4. mvcc原理_MVCC原理探究及MySQL源码实现分析

    沃趣科技数据库专家  董红禹 MVCC原理探究及MySQL源码实现分析 数据库多版本读场景 session 1 session 2 select a from test; return a = 10 ...

  5. [原] KVM 虚拟化原理探究(6)— 块设备IO虚拟化

    目录 KVM 虚拟化原理探究(6)- 块设备IO虚拟化 块设备IO虚拟化简介 传统块设备架构 块设备IO协议栈 块设备IO流程 块设备IO虚拟化 总结 KVM 虚拟化原理探究(6)- 块设备IO虚拟化 ...

  6. 计算机信息管理系统设计原理探究,计算机信息管理系统设计原理探究

    盛巍 摘 要:在计算机信息技术发展和应用速度不断提升的背景之下,我国社会各个行业的计算机信息管理系统需求不断提升.人们可以通过计算机信息管理系统收集自己需要的信息资料,并对数据信息进行分析,在各项决策 ...

  7. KVM 虚拟化原理探究(5)— 网络IO虚拟化

    2019独角兽企业重金招聘Python工程师标准>>> IO 虚拟化简介 前面的文章介绍了KVM的启动过程,CPU虚拟化,内存虚拟化原理.作为一个完整的风诺依曼计算机系统,必然有输入 ...

  8. 浏览器工作原理探究详解

    浏览器工作原理探究 标签: 浏览器工作原理 / web性能优化 引言 最近对web的性能优化比较感兴趣,而前端代码主要在浏览器工作的.如果对浏览器的工作原理了解清楚,可以为web性能优化提供方向以及理 ...

  9. 简述 OAuth 2.0 的运作流程

    本文将以用户使用 github 登录网站留言为例,简述 OAuth 2.0 的运作流程. 假如我有一个网站,你是我网站上的访客,看了文章想留言表示「朕已阅」,留言时发现有这个网站的帐号才能够留言,此时 ...

最新文章

  1. valgrind——hisi平台valgrind
  2. SAP QUERY这个工具的使用
  3. 客户端到服务器的请求响应时间,客户端到服务器的网络响应时间
  4. leaflet调用mysql_Leaflet地图框架使用手册——L.Path
  5. html语言中的转行标记是6,网页设计与制作模拟试题
  6. 【每日SQL打卡】​​​​​​​​​​​​​​​DAY 19丨行转列【难度中等】​
  7. Jquery - jquery 插件,jQuery.Switchable
  8. 取消vs2013在代码中的Reference数量功能
  9. 在Linux中如何使用gdb调试C程序
  10. linux创建环境变量有什么用,环境变量和shell变量到底有什么区别呢?
  11. Day0205____数据库
  12. 阿里月饼事件被辞程序员冤吗?
  13. Day5-ESP8266模块——百问网7天物联网智能家居
  14. 研究揭示肿瘤基因突变检测的复杂性
  15. ccf 201809-4 再卖菜
  16. 微信小程序和PWA对比分析
  17. shell的转义字符
  18. 零基础学 Python 有什么建议?
  19. Photoshop Scripting 高阶编程(1):取色器的应用
  20. Excel-VBA操作文件四大方法

热门文章

  1. (数据库系统概论|王珊)第七章数据库设计-第三节:概念结构设计
  2. 【原创】从头开始,使用安卓系统WebView做一个功能强大的Epub阅读器(一)
  3. Must call super constructor in derived class before accessing or returning from derived const
  4. 安装Alpine操作系统
  5. 系列解读Dropout
  6. TypeScript中any与unknown的区别
  7. java 生成随机md5_Java常用工具类(计算MD5,验证码随机生成,天数差值计算)
  8. Android app 内存分配
  9. 使用pscp命令将Windows和linux中文件互相拷贝
  10. 打开Flutter动画的另一种姿势——Flare,android面试题选择题