【第三篇】开始和SpringSecurity的简单交往-处理请求的本质

【第三篇】开始和SpringSecurity的简单交往-处理请求的本质,第1张

SpringSecurity请求流转的本质 1. SpringSecurity核心源码分析

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

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

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




  Archetype Created Web Application

  
  
    contextConfigLocation
    classpath:applicationContext.xml
  
  
    org.springframework.web.context.ContextLoaderListener
  

  
  
    CharacterEncodingFilter
    org.springframework.web.filter.CharacterEncodingFilter
    
      encoding
      utf-8
    
  
  
    CharacterEncodingFilter
    /*
  
  
  
    dispatcherServletb
    org.springframework.web.servlet.DispatcherServlet
    
    
      contextConfigLocation
      classpath:spring-mvc.xml
    
    1
  
  
    dispatcherServletb
    
    /
  

  
  
    springSecurityFilterChain
    org.springframework.web.filter.DelegatingFilterProxy
  
  
    springSecurityFilterChain
    /*
  



web.xml中配置的信息:

  1. Spring的初始化(会加载解析SpringSecurity的配置文件)
  2. SpringMVC的前端控制器初始化
  3. 加载DelegatingFilterProxy过滤器

Spring的初始化 *** 作和SpringSecurity有关系的 *** 作是,会加载介绍SpringSecurity的配置文件,将相关的数据添加到Spring容器中

SpringMVC的初始化和SpringSecurity其实是没有多大关系的

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

系统启动的时候会执行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();
            }
// 获取Spring的容器对象
            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属性

1.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);
    }
}

获取到了对应处理请求的过滤器链

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);
    }

}

主要过滤器的介绍

https://www.processon.com/view/link/5f7b197ee0b34d0711f3e955

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("\n\n  \n    \n    \n    \n    \n    Please sign in\n    \n    \n  \n  \n     \n");
    String contextPath = request.getContextPath();
    if (this.formLoginEnabled) {
        sb.append("      
+ contextPath + this.authenticationUrl + "\">\n Please sign in\n" + createError(loginError, errorMsg) + createLogoutSuccess(logoutSuccess) + "

\n \n + this.usernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n

\n

\n \n + this.passwordParameter + "\" class=\"form-control\" placeholder=\"Password\" required>\n

\n"
+ this.createRememberMe(this.rememberMeParameter) + this.renderHiddenInputs(request) + " \n \n"
); } if (this.openIdEnabled) { sb.append("
+ contextPath + this.openIDauthenticationUrl + "\">\n Login with OpenID Identity\n" + createError(loginError, errorMsg) + createLogoutSuccess(logoutSuccess) + "

\n \n + this.openIDusernameParameter + "\" class=\"form-control\" placeholder=\"Username\" required autofocus>\n

\n"
+ this.createRememberMe(this.openIDrememberMeParameter) + this.renderHiddenInputs(request) + " \n \n"
); } if (this.oauth2LoginEnabled) { sb.append("Login with OAuth 2.0"); sb.append(createError(loginError, errorMsg)); sb.append(createLogoutSuccess(logoutSuccess)); sb.append("\n");Iterator var7 =this.oauth2AuthenticationUrlToClientName.entrySet().iterator();while(var7.hasNext()){Entry<String, String> clientAuthenticationUrlToClientName =(Entry)var7.next(); sb.append(" \n");} sb.append("
"); String url = (String)clientAuthenticationUrlToClientName.getKey(); sb.append(").append(contextPath).append(url).append("\">"); String clientName = HtmlUtils.htmlEscape((String)clientAuthenticationUrlToClientName.getValue()); sb.append(clientName); sb.append(""); sb.append("
\n"
); } sb.append("\n"); sb.append(""); return sb.toString(); }

第一次请求的完整的流程

页面调试也可以验证我的推论

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/721941.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存