• SpringSecurity原理:探究SpringSecurity运作流程


    SpringSecurity原理

    注意:本小节内容作为选学内容,但是难度比前两章的源码部分简单得多。

    最后我们再来聊一下SpringSecurity的实现原理,它本质上是依靠N个Filter实现的,也就是一个完整的过滤链(注意这里是过滤器,不是拦截器

    我们就从AbstractSecurityWebApplicationInitializer开始下手,我们来看看它配置了什么:

    1. //此方法会在启动时被调用
    2. public final void onStartup(ServletContext servletContext) {
    3. this.beforeSpringSecurityFilterChain(servletContext);
    4. if (this.configurationClasses != null) {
    5. AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
    6. rootAppContext.register(this.configurationClasses);
    7. servletContext.addListener(new ContextLoaderListener(rootAppContext));
    8. }
    9. if (this.enableHttpSessionEventPublisher()) {
    10. servletContext.addListener("org.springframework.security.web.session.HttpSessionEventPublisher");
    11. }
    12. servletContext.setSessionTrackingModes(this.getSessionTrackingModes());
    13. //重点在这里,这里插入了关键的FilterChain
    14. this.insertSpringSecurityFilterChain(servletContext);
    15. this.afterSpringSecurityFilterChain(servletContext);
    16. }
    1. private void insertSpringSecurityFilterChain(ServletContext servletContext) {
    2. String filterName = "springSecurityFilterChain";
    3. //创建了一个DelegatingFilterProxy对象,它本质上也是一个Filter
    4. DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(filterName);
    5. String contextAttribute = this.getWebApplicationContextAttribute();
    6. if (contextAttribute != null) {
    7. springSecurityFilterChain.setContextAttribute(contextAttribute);
    8. }
    9. //通过ServletContext注册DelegatingFilterProxy这个Filter
    10. this.registerFilter(servletContext, true, filterName, springSecurityFilterChain);
    11. }

    我们接着来看看,DelegatingFilterProxy在做什么:

     

    1. //这个是初始化方法,它由GenericFilterBean(父类)定义,在afterPropertiesSet方法中被调用
    2. protected void initFilterBean() throws ServletException {
    3. synchronized(this.delegateMonitor) {
    4. if (this.delegate == null) {
    5. if (this.targetBeanName == null) {
    6. this.targetBeanName = this.getFilterName();
    7. }
    8. WebApplicationContext wac = this.findWebApplicationContext();
    9. if (wac != null) {
    10. //耐心点,套娃很正常
    11. this.delegate = this.initDelegate(wac);
    12. }
    13. }
    14. }
    15. }
    1. protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
    2. String targetBeanName = this.getTargetBeanName();
    3. Assert.state(targetBeanName != null, "No target bean name set");
    4. //这里通过WebApplicationContext获取了一个Bean
    5. Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
    6. if (this.isTargetFilterLifecycle()) {
    7. delegate.init(this.getFilterConfig());
    8. }
    9. //返回Filter
    10. return delegate;
    11. }

    这里我们需要添加一个断点来查看到底获取到了什么Bean。

    通过断点调试,我们发现这里放回的对象是一个FilterChainProxy类型的,并且调用了它的初始化方法,但是FilterChainProxy类中并没有重写init方法或是initFilterBean方法。

    我们倒回去看,当Filter返回之后,DelegatingFilterProxy的一个成员变量delegate被赋值为得到的Filter,也就是FilterChainProxy对象,接着我们来看看,DelegatingFilterProxy是如何执行doFilter方法的。

    1. public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    2. Filter delegateToUse = this.delegate;
    3. if (delegateToUse == null) {
    4. //非正常情况,这里省略...
    5. }
    6. //这里才是真正的调用,别忘了delegateToUse就是初始化的FilterChainProxy对象
    7. this.invokeDelegate(delegateToUse, request, response, filterChain);
    8. }
    1. protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    2. //最后实际上调用的是FilterChainProxy的doFilter方法
    3. delegate.doFilter(request, response, filterChain);
    4. }

    所以我们接着来看,FilterChainProxy的doFilter方法又在干什么:

    1. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    2. boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
    3. if (!clearContext) {
    4. //真正的过滤在这里执行
    5. this.doFilterInternal(request, response, chain);
    6. } else {
    7. //...
    8. }
    9. }
    1. private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    2. FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
    3. HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
    4. //这里获取了一个Filter列表,实际上SpringSecurity就是由N个过滤器实现的,这里获取的都是SpringSecurity提供的过滤器
    5. //但是请注意,经过我们之前的分析,实际上真正注册的Filter只有DelegatingFilterProxy
    6. //而这里的Filter列表中的所有Filter并没有被注册,而是在这里进行内部调用
    7. List filters = this.getFilters((HttpServletRequest)firewallRequest);
    8. //只要Filter列表不是空,就依次执行内置的Filter
    9. if (filters != null && filters.size() != 0) {
    10. if (logger.isDebugEnabled()) {
    11. logger.debug(LogMessage.of(() -> {
    12. return "Securing " + requestLine(firewallRequest);
    13. }));
    14. }
    15. //这里创建一个虚拟的过滤链,过滤流程是由SpringSecurity自己实现的
    16. FilterChainProxy.VirtualFilterChain virtualFilterChain = new FilterChainProxy.VirtualFilterChain(firewallRequest, chain, filters);
    17. //调用虚拟过滤链的doFilter
    18. virtualFilterChain.doFilter(firewallRequest, firewallResponse);
    19. } else {
    20. if (logger.isTraceEnabled()) {
    21. logger.trace(LogMessage.of(() -> {
    22. return "No security for " + requestLine(firewallRequest);
    23. }));
    24. }
    25. firewallRequest.reset();
    26. chain.doFilter(firewallRequest, firewallResponse);
    27. }
    28. }

    我们来看一下虚拟过滤链的doFilter是怎么处理的:

    1. //看似没有任何循环,实际上就是一个循环,是一个递归调用
    2. public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
    3. //判断是否已经通过全部的内置过滤器,定位是否等于当前大小
    4. if (this.currentPosition == this.size) {
    5. if (FilterChainProxy.logger.isDebugEnabled()) {
    6. FilterChainProxy.logger.debug(LogMessage.of(() -> {
    7. return "Secured " + FilterChainProxy.requestLine(this.firewalledRequest);
    8. }));
    9. }
    10. this.firewalledRequest.reset();
    11. //所有的内置过滤器已经完成,按照正常流程走DelegatingFilterProxy的下一个Filter
    12. //也就是说这里之后就与DelegatingFilterProxy没有任何关系了,该走其他过滤器就走其他地方配置的过滤器,SpringSecurity的过滤操作已经结束
    13. this.originalChain.doFilter(request, response);
    14. } else {
    15. //定位自增
    16. ++this.currentPosition;
    17. //获取当前定位的Filter
    18. Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
    19. if (FilterChainProxy.logger.isTraceEnabled()) {
    20. FilterChainProxy.logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(), this.currentPosition, this.size));
    21. }
    22. //执行内部过滤器的doFilter方法,传入当前对象本身作为Filter,执行如果成功,那么一定会再次调用当前对象的doFilter方法
    23. //可能最不理解的就是这里,执行的难道不是内部其他Filter的doFilter方法吗,怎么会让当前对象的doFilter方法递归调用呢?
    24. //没关系,了解了其中一个内部过滤器就明白了
    25. nextFilter.doFilter(request, response, this);
    26. }
    27. }

    因此,我们差不多已经了解了整个SpringSecurity的实现机制了,那么我们来看几个内部的过滤器分别在做什么。

    比如用于处理登陆的过滤器UsernamePasswordAuthenticationFilter,它继承自AbstractAuthenticationProcessingFilter,我们来看看它是怎么进行过滤的:

    1. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    2. this.doFilter((HttpServletRequest)request, (HttpServletResponse)response, chain);
    3. }
    4. private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
    5. //如果不是登陆请求,那么根本不会理这个请求
    6. if (!this.requiresAuthentication(request, response)) {
    7. //直接调用传入的FilterChain的doFilter方法
    8. //而这里传入的正好是VirtualFilterChain对象
    9. //这下知道为什么上面说是递归了吧
    10. chain.doFilter(request, response);
    11. } else {
    12. //如果是登陆请求,那么会执行登陆请求的相关逻辑,注意执行过程中出现任何问题都会抛出异常
    13. //比如用户名和密码错误,我们之前也已经测试过了,会得到一个BadCredentialsException
    14. try {
    15. //进行认证
    16. Authentication authenticationResult = this.attemptAuthentication(request, response);
    17. if (authenticationResult == null) {
    18. return;
    19. }
    20. this.sessionStrategy.onAuthentication(authenticationResult, request, response);
    21. if (this.continueChainBeforeSuccessfulAuthentication) {
    22. chain.doFilter(request, response);
    23. }
    24. //如果一路绿灯,没有报错,那么验证成功,执行successfulAuthentication
    25. this.successfulAuthentication(request, response, chain, authenticationResult);
    26. } catch (InternalAuthenticationServiceException var5) {
    27. this.logger.error("An internal error occurred while trying to authenticate the user.", var5);
    28. //验证失败,会执行unsuccessfulAuthentication
    29. this.unsuccessfulAuthentication(request, response, var5);
    30. } catch (AuthenticationException var6) {
    31. this.unsuccessfulAuthentication(request, response, var6);
    32. }
    33. }
    34. }

    那么我们来看看successfulAuthentication和unsuccessfulAuthentication分别做了什么:

    1. protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    2. //向SecurityContextHolder添加认证信息,我们可以通过SecurityContextHolder对象获取当前登陆的用户
    3. SecurityContextHolder.getContext().setAuthentication(authResult);
    4. if (this.logger.isDebugEnabled()) {
    5. this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", authResult));
    6. }
    7. //记住我实现
    8. this.rememberMeServices.loginSuccess(request, response, authResult);
    9. if (this.eventPublisher != null) {
    10. this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
    11. }
    12. //调用默认的或是我们自己定义的AuthenticationSuccessHandler的onAuthenticationSuccess方法
    13. //这个根据我们配置文件决定
    14. //到这里其实页面就已经直接跳转了
    15. this.successHandler.onAuthenticationSuccess(request, response, authResult);
    16. }
    17. protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
    18. //登陆失败会直接清理掉SecurityContextHolder中的认证信息
    19. SecurityContextHolder.clearContext();
    20. this.logger.trace("Failed to process authentication request", failed);
    21. this.logger.trace("Cleared SecurityContextHolder");
    22. this.logger.trace("Handling authentication failure");
    23. //登陆失败的记住我处理
    24. this.rememberMeServices.loginFail(request, response);
    25. //同上,调用默认或是我们自己定义的AuthenticationFailureHandler
    26. this.failureHandler.onAuthenticationFailure(request, response, failed);
    27. }

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

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

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

  • 相关阅读:
    懒加载指令实现
    企业数据安全如何落实?私有化知识文档管理系统效率部署
    计算机毕业设计源代码java项目开发实例基于SSM的车库停车计费系统|停车场[包运行成功]
    以自主技术跃进的综合冲压的顶级制造商
    在线负载离线负载与在线算法离线算法
    关于wukong-kong项目在树莓派启动后只运行一次卡死的问题的解决方法
    GET和POST的区别
    莫名其妙的越界错误原因之条件判断顺序——基于LeetCode 99题,恢复二叉搜索树
    电脑重装系统后Win7打印机无法打印该如何处理?
    React学习笔记(番外一)——video.js视频播放组件的入门及排坑经历
  • 原文地址:https://blog.csdn.net/Leon_Jinhai_Sun/article/details/126909196