02.SpringSecurity高级应用——浅析核心流程源码

前言

分析SpringSecurity的核心原理的时候,那么我们要从哪开始分析?以及我们要分析哪些内容?

  1. 系统启动的时候SpringSecurity做了哪些事情?
  2. 第一次请求执行的流程是什么?
  3. SpringSecurity中认证流程是怎么样的?

1.系统启动

当我们的Web服务启动的时候,SpringSecurity做了哪些事情?
当系统启动的时候,肯定会加载我们的配置的web.xml文件

1.1 加载web.xml

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app  version="2.5"
          xmlns="http://java.sun.com/xml/ns/javaee"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <display-name>Archetype Created Web Application</display-name>

  <!--初始化spring容器-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!--
  配置一个SpringMVC的前端控制器
  目的是所有的客户端的请求都会被DispatcherServlet处理
  -->
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
  </servlet>



  <servlet-mapping>
    <!--支持Restful风格编程-->
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <!--配置字符编码的过滤器-->
  <!--设置设置编码的过滤器-->
  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>forceRequestEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
    <init-param>
      <param-name>forceResponseEncoding</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!--配置过滤器链 springSecurityFilterChain 名称固定-->
  <filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!--default 防止静态资源-->
  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.html</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.css</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.js</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.jpg</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.png</url-pattern>
  </servlet-mapping>


</web-app>


web.xml中配置的信息:

  1. Spring的初始化(会加载解析SpringSecurity的配置文件)
  2. SpringMVC的前端控制器初始化(SpringMVC的初始化和SpringSecurity其实是没有多大关系的)
  3. 加载DelegatingFilterProxy过滤器(拦截所有的请求。而且这个过滤器本身适合SpringSecurity是没有关系的!!!!在之前介绍的Shiro的时候,和Spring整合的时候我们也是使用的这个过滤器。其实就是完成从IOC容器中获取DelegatingFilterProxy这个过滤器配置的FilterName的对象。)

1.1.1 Spring初始化加载解析SpringSecurity源码浅析

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.1.2 DelegatingFilterProxy过滤器

拦截所有的请求。而且这个过滤器本身适合SpringSecurity是没有关系的!!!!在之前介绍的Shiro的时候,和Spring整合的时候我们也是使用的这个过滤器。其实就是完成从IOC容器中获取DelegatingFilterProxy这个过滤器配置的FilterName的对象。

系统启动的时候会执行DelegatingFilterProxy的init方法

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

    protected void initFilterBean() throws ServletException {
        synchronized(this.delegateMonitor) {
        //如果委托对象为 null 进入
            if (this.delegate == null) {
            // 如果targetBeanName == null
                if (this.targetBeanName == null) {
                // targetBeanName = 'springSecurityFilterChain'
                    this.targetBeanName = this.getFilterName();
                }

                WebApplicationContext wac = this.findWebApplicationContext();
                if (wac != null) {
                //初始化代理对象
                    this.delegate = this.initDelegate(wac);
                }
            }

        }
    }
    protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
    //SpringSecurityFilterChain
        String targetBeanName = this.getTargetBeanName();
        Assert.state(targetBeanName != null, "No target bean name set");
        //从IOC容器中获取SpringSecurityFilterChain的类型为Filter的对象
        Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
        if (this.isTargetFilterLifecycle()) {
            delegate.init(this.getFilterConfig());
        }

        return delegate;
    }

init方法的作用是:从IOC容器中获取FilterChainProxy的实例对象,并赋值给DelegatingFilterProxy的delegate属性。

2.第一次请求

客户发送请求会经过很多个Web Filter拦截。
在这里插入图片描述
然后经过系统启动的分析,我们知道有一个我们定义的过滤器会拦截客户端的所有的请求。DelegatingFilterProxy

在这里插入图片描述
当用户请求进来的时候会被doFilter方法拦截
在这里插入图片描述

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
        // 如果delegateToUse 为空 那么完成init中的初始化操作
            synchronized(this.delegateMonitor) {
                delegateToUse = this.delegate;
                if (delegateToUse == null) {
                    WebApplicationContext wac = this.findWebApplicationContext();
                    if (wac == null) {
                        throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
                    }

                    delegateToUse = this.initDelegate(wac);
                }

                this.delegate = delegateToUse;
            }
        }

        this.invokeDelegate(delegateToUse, request, response, filterChain);
    }

invokeDelegate

    protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    //delegate.doFilter() FilterChainProxy
        delegate.doFilter(request, response, filterChain);
    }

所以在此处我们发现DelegatingFilterProxy最终是调用的委托代理对象的doFilter方法
在这里插入图片描述
FilterChainProxy
过滤器链的代理对象:增强过滤器链(具体处理请求的过滤器还不是FilterChainProxy)根据客户端的请求匹配合适的过滤器链来处理请求

public class FilterChainProxy extends GenericFilterBean {
    private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
    private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
    // 过滤器链的集合 保存的有很多个过滤器链 一个过滤器链中包含的有多个过滤器
    private List<SecurityFilterChain> filterChains;
    private FilterChainProxy.FilterChainValidator filterChainValidator;
    private HttpFirewall firewall;
    .....
    }

在这里插入图片描述

//处理用户请求
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (clearContext) {
            try {
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                this.doFilterInternal(request, response, chain);
            } finally {
                SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }
        } else {
            this.doFilterInternal(request, response, chain);
        }

    }

doFilterInternal

    private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
        HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
        // 根据当前的请求获取对应的过滤器链
        List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);
        if (filters != null && filters.size() != 0) {
            FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters);
            vfc.doFilter(fwRequest, fwResponse);
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null ? " has no matching filters" : " has an empty filter list"));
            }

            fwRequest.reset();
            chain.doFilter(fwRequest, fwResponse);
        }
    }

getFilters

    private List<Filter> getFilters(HttpServletRequest request) {
        Iterator var2 = this.filterChains.iterator();

        SecurityFilterChain chain;
        do {
            if (!var2.hasNext()) {
                return null;
            }

            chain = (SecurityFilterChain)var2.next();
        } while(!chain.matches(request));

        return chain.getFilters();
   

SpringSecurity中处理请求的过滤器中具体处理请求的方法

        public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
            if (this.currentPosition == this.size) {
                if (FilterChainProxy.logger.isDebugEnabled()) {
                    FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest) + " reached end of additional filter chain; proceeding with original chain");
                }

                this.firewalledRequest.reset();
                this.originalChain.doFilter(request, response);
            } else {
                ++this.currentPosition;
                Filter nextFilter = (Filter)this.additionalFilters.get(this.currentPosition - 1);
                if (FilterChainProxy.logger.isDebugEnabled()) {
                    FilterChainProxy.logger.debug(UrlUtils.buildRequestUrl(this.firewalledRequest) + " at position " + this.currentPosition + " of " + this.size + " in additional filter chain; firing Filter: '" + nextFilter.getClass().getSimpleName() + "'");
                }

                nextFilter.doFilter(request, response, this);
            }

        }

在这里插入图片描述
ExceptionTranslationFilter
ExceptionTranslationFilter是我们看到的过滤器链中的倒数第二个,作用是捕获倒数第一个过滤器抛出来的异常信息。
在这里插入图片描述
FilterSecurityInterceptor
做权限相关的内容

    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        if (fi.getRequest() != null && fi.getRequest().getAttribute("__spring_security_filterSecurityInterceptor_filterApplied") != null && this.observeOncePerRequest) {
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } else {
            if (fi.getRequest() != null && this.observeOncePerRequest) {
                fi.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);
            }
			// 抛出异常 ExceptionTranslationFilter就会捕获异常
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            } finally {
                super.finallyInvocation(token);
            }

            super.afterInvocation(token, (Object)null);
        }

    }

ExceptionTranslationFilter处理异常的代码
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当用第二次提交http://localhost:8082/login时,我们要关注的是 DefaultLoginPageGeneratingFilter 这个过滤器

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        boolean loginError = this.isErrorPage(request);
        boolean logoutSuccess = this.isLogoutSuccess(request);
        if (!this.isLoginUrlRequest(request) && !loginError && !logoutSuccess) {
        //正常的业务请求就直接放过
            chain.doFilter(request, response);
        } else {
        // 需要跳转到登录页面的请求
            String loginPageHtml = this.generateLoginPageHtml(request, loginError, logoutSuccess);
            //直接相应登录页面
            response.setContentType("text/html;charset=UTF-8");
            response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
            response.getWriter().write(loginPageHtml);
        }
    }

generateLoginPageHtml

    private String generateLoginPageHtml(HttpServletRequest request, boolean loginError, boolean logoutSuccess) {
        String errorMsg = "Invalid credentials";
        if (loginError) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                AuthenticationException ex = (AuthenticationException)session.getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
                errorMsg = ex != null ? ex.getMessage() : "Invalid credentials";
            }
        }

        StringBuilder sb = new StringBuilder();
        sb.append("<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n    <meta name=\"description\" content=\"\">\n    <meta name=\"author\" content=\"\">\n    <title>Please sign in</title>\n    <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n    <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n  </head>\n  <body>\n     <div class=\"container\">\n");
        String contextPath = request.getContextPath();
        if (this.formLoginEnabled) {
            sb.append("      <form class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.authenticationUrl + "\">\n        <h2 class=\"form-signin-heading\">Please sign in</h2>\n" + createError(loginError, errorMsg) + createLogoutSuccess(logoutSuccess) + "        <p>\n          <label for=\"username\" class=\"sr-only\">Username</label>\n          <input type=\"text\" id=\"username\" name=\"" + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n        </p>\n        <p>\n          <label for=\"password\" class=\"sr-only\">Password</label>\n          <input type=\"password\" id=\"password\" name=\"" + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n        </p>\n" + this.createRememberMe(this.rememberMeParameter) + this.renderHiddenInputs(request) + "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n      </form>\n");
        }

        if (this.openIdEnabled) {
            sb.append("      <form name=\"oidf\" class=\"form-signin\" method=\"post\" action=\"" + contextPath + this.openIDauthenticationUrl + "\">\n        <h2 class=\"form-signin-heading\">Login with OpenID Identity</h2>\n" + createError(loginError, errorMsg) + createLogoutSuccess(logoutSuccess) + "        <p>\n          <label for=\"username\" class=\"sr-only\">Identity</label>\n          <input type=\"text\" id=\"username\" name=\"" + this.openIDusernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n        </p>\n" + this.createRememberMe(this.openIDrememberMeParameter) + this.renderHiddenInputs(request) + "        <button class=\"btn btn-lg btn-primary btn-block\" type=\"submit\">Sign in</button>\n      </form>\n");
        }

        if (this.oauth2LoginEnabled) {
            sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h2>");
            sb.append(createError(loginError, errorMsg));
            sb.append(createLogoutSuccess(logoutSuccess));
            sb.append("<table class=\"table table-striped\">\n");
            Iterator var7 = this.oauth2AuthenticationUrlToClientName.entrySet().iterator();

            while(var7.hasNext()) {
                Entry<String, String> clientAuthenticationUrlToClientName = (Entry)var7.next();
                sb.append(" <tr><td>");
                String url = (String)clientAuthenticationUrlToClientName.getKey();
                sb.append("<a href=\"").append(contextPath).append(url).append("\">");
                String clientName = HtmlUtils.htmlEscape((String)clientAuthenticationUrlToClientName.getValue());
                sb.append(clientName);
                sb.append("</a>");
                sb.append("</td></tr>\n");
            }

            sb.append("</table>\n");
        }

        sb.append("</div>\n");
        sb.append("</body></html>");
        return sb.toString();
    }

3.认证流程

UsernamePasswordAuthenticationFilter:专门处理用户认证请求的

在父类中AbstractAuthenticationProcessingFilter中查看doFilter的逻辑

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        if (!this.requiresAuthentication(request, response)) {
        // 如果不是必须要认证的请求就直接放过
            chain.doFilter(request, response);
        } else {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Request is to process authentication");
            }
		
            Authentication authResult;
            try {
            	// 获取认证的信息 客户端提交的表单信息
                authResult = this.attemptAuthentication(request, response);
                if (authResult == null) {
                    return;
                }

                this.sessionStrategy.onAuthentication(authResult, request, response);
            } catch (InternalAuthenticationServiceException var8) {
                this.logger.error("An internal error occurred while trying to authenticate the user.", var8);
                this.unsuccessfulAuthentication(request, response, var8);
                return;
            } catch (AuthenticationException var9) {
                this.unsuccessfulAuthentication(request, response, var9);
                return;
            }

            if (this.continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }

            this.successfulAuthentication(request, response, chain, authResult);
        }
    }

attemptAuthentication在UsernamePasswordAuthenticationFilter实现

    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }
			// 空处理
            username = username.trim();
            // 账号密码封装为对应的对象
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            // 认证操作
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

authenticate
在这里插入图片描述
变量获取每个认证提供者,然后认证处理
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
此处就会进入到我们自定义的UserServiceImpl中
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值