Spring Security 对请求的处理流程

简介: 分析Spring Security的核心原理,可以从以下几个方面进行:1. 系统启动的时候Spring Security做了哪些事情?2. 发起一次请求后Spring Security做了哪些事情?

@[TOC]

前言

分析Spring Security的核心原理,可以从以下几个方面进行:

  1. 系统启动的时候Spring Security做了哪些事情?
  2. 发起一次请求后Spring Security做了哪些事情?

系统启动

当我们的Web服务启动的时候,SpringSecurity做了哪些事情?当系统启动的时候,首先会加载配置的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" id="WebApp_ID" 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>

  <!-- post乱码过滤器 -->
  <filter>
    <filter-name>CharacterEncodingFilter</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>
  </filter>
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <!-- 前端控制器 -->
  <servlet>
    <servlet-name>dispatcherServletb</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- contextConfigLocation不是必须的, 如果不配置contextConfigLocation, springmvc的配置文件默认在:WEB-INF/servlet的name+"-servlet.xml" -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServletb</servlet-name>
    <!-- 拦截所有请求jsp除外 -->
    <url-pattern>/</url-pattern>
  </servlet-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>

</web-app>

web.xml中与SpringSecurity相关的配置信息:

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

Spring的初始化操作会加载SpringSecurity的配置文件,将相关的数据添加到Spring容器中

image.png

DelegatingFilterProxy过滤器:这个过滤器本身是和SpringSecurity没有关系的,会拦截所有的请求,包括SpringSecurity相关的过滤器。

Spring Security 对请求的处理

客户发送一个请求会经过很多个Web Filter拦截,web.xml中 定义的 DelegatingFilterProxy 过滤器会拦截客户端的所有的请求。

image.png

当用户请求进来的时候会被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 ) 根据客户端的请求匹配合适的过滤器链链来处理请求,如下

image.png

// 处理用户请求
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 获取对应处理请求的过滤器链,然后一一处理。

image.png

CSRFFilter:是Spring Security中用于防止跨站请求伪造(CSRF)攻击的过滤器。CSRFFilter通过在请求到达目标资源之前,验证请求的来源和身份认证信息是否匹配来防止CSRF攻击。它会在处理请求之前检查请求是否符合以下条件:

  • 请求是否来自已认证的用户。
  • 请求是否与目标资源的身份认证信息匹配。

如果以上两个条件都满足,则请求被允许继续处理,否则将被拒绝或者重定向到登录页面。

UsernamePasswordAuthenticationFilter:是Spring Security用来验证登陆用户密码的过滤器,在[认证流程]中有详细说明。

DefaultLoginPageGeneratingFilter:是Spring Security中负责生成登录页面的过滤器。当开发人员在安全配置中没有配置登录页面时,Spring Security Web会自动构造一个登录页面给用户。

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

ExceptionTranslationFilter:是滤器链中的倒数第二个,如果没有登录时进行请求,该过滤器会重定向到配置的登录页面。

在这里插入图片描述

总结

通过以上的分析,可以知道Spring Security使用一系列的过滤器来提供其安全功能。包括CsrfFilter、UsernamePasswordAuthenticationFilter、DefaultLoginPageGeneratingFilter、ExceptionTranslationFilter等等,它们在Spring Security中扮演着各自的角色,协同工作以提供完善的安全保障。

相关文章
|
3月前
|
缓存 安全 Java
Spring Get请求 与post请求
本文详细介绍了Spring框架中GET请求和POST请求的区别及应用场景。GET请求用于从服务器获取资源,参数附在URL末尾,适合查看非敏感信息;POST请求用于向服务器提交数据,参数在请求体中传输,适合处理敏感信息。Spring通过`@GetMapping`和`@PostMapping`注解分别处理这两种请求。此外,文章还提供了示例代码,展示了如何在Spring中实现这两种请求的处理。最后,文章总结了推荐使用POST请求的原因,包括更高的安全性、更大的数据传输量、更好的幂等性及灵活性。
Spring Get请求 与post请求
|
2月前
|
设计模式 前端开发 Java
Spring MVC——项目创建和建立请求连接
MVC是一种软件架构设计模式,将应用分为模型、视图和控制器三部分。Spring MVC是基于MVC模式的Web框架,通过`@RequestMapping`等注解实现URL路由映射,支持GET和POST请求,并可传递参数。创建Spring MVC项目与Spring Boot类似,使用`@RestController`注解标记控制器类。
39 1
Spring MVC——项目创建和建立请求连接
|
3月前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
256 24
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
2月前
|
前端开发 Java Spring
【Spring】“请求“ 之后端传参重命名,传递数组、集合,@PathVariable,@RequestPart
【Spring】“请求“ 之后端传参重命名,传递数组、集合,@PathVariable,@RequestPart
34 2
|
2月前
|
JSON 前端开发 Java
【Spring】“请求“ 之传递 JSON 数据
【Spring】“请求“ 之传递 JSON 数据
87 2
|
2月前
|
前端开发 Java Spring
【Spring】“请求“ 之传递单个参数、传递多个参数和传递对象
【Spring】“请求“ 之传递单个参数、传递多个参数和传递对象
121 2
|
2月前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
199 2
|
2月前
|
JSON 前端开发 JavaScript
优雅!Spring Boot 3.3 实现职责链模式,轻松应对电商订单流程
本文介绍如何使用 Spring Boot 3.3 实现职责链模式,优化电商订单处理流程。通过将订单处理的各个环节(如库存校验、优惠券核验、支付处理等)封装为独立的处理器,并通过职责链将这些处理器串联起来,实现了代码的解耦和灵活扩展。具体实现包括订单请求类 `OrderRequest`、抽象处理器类 `OrderHandler`、具体处理器实现(如 `OrderValidationHandler`、`VerifyCouponHandler` 等)、以及初始化职责链的配置类 `OrderChainConfig`。
|
4月前
|
安全 Java 应用服务中间件
如何在 Spring Boot 3.3 中实现请求 IP 白名单拦截功能
【8月更文挑战第30天】在构建Web应用时,确保应用的安全性是至关重要的。其中,对访问者的IP地址进行限制是一种常见的安全措施,特别是通过实施IP白名单策略,可以只允许特定的IP地址或IP段访问应用,从而有效防止未授权的访问。在Spring Boot 3.3中,我们可以通过多种方式实现这一功能,下面将详细介绍几种实用的方法。
270 1
|
4月前
|
Java API UED
【实战秘籍】Spring Boot开发者的福音:掌握网络防抖动,告别无效请求,提升用户体验!
【8月更文挑战第29天】网络防抖动技术能有效处理频繁触发的事件或请求,避免资源浪费,提升系统响应速度与用户体验。本文介绍如何在Spring Boot中实现防抖动,并提供代码示例。通过使用ScheduledExecutorService,可轻松实现延迟执行功能,确保仅在用户停止输入后才触发操作,大幅减少服务器负载。此外,还可利用`@Async`注解简化异步处理逻辑。防抖动是优化应用性能的关键策略,有助于打造高效稳定的软件系统。
77 2