SpringSecurity的防Csrf攻击实现代码解析

本文将详细介绍Spring Security中防范Csrf攻击的实现代码解析。

什么是Csrf攻击

Csrf全称为Cross-site request forgery,即跨站请求伪造。它利用用户在已经登录的网站中的权限来进行恶意攻击,而用户却毫不知情。攻击者可以通过各种方式获取并篡改用户的Cookie,再利用这些Cookie发起跨站请求伪造攻击,使得受害者被误导到执行某些意料之外的操作。

SpringSecurity中的Csrf攻击防御

SpringSecurity中提供了防范Csrf攻击的实现,包括Token的生成和验证。当用户访问页面时,服务器会将一个固定的Token放在该页面的表单中或者请求头中,在后续的请求中,服务器会校验这个Token是否匹配,如果不匹配,则拒绝该请求。这样就防止了Csrf攻击。

SpringSecurity中Csrf攻击防御的实现

1.启用Csrf攻击防御

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
  ...
  @Override protected void configure(HttpSecurity http) throws Exception {
    ...
    http.csrf().csrfTokenRepository(csrfTokenRepository())
    .and().addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
  }
  ...
}

在Spring Security中启用Csrf攻击的防御,需要在configure方法中配置http.csrf()。需要注意的是,csrf()方法返回的是一个CsrfConfigurer对象。通过其csrfTokenRepository方法可以设置此对象用于持久化CsrfToken的CsrfTokenRepository,而通过csrfHeaderFilter方法可以指定当Csrf攻击发生时,Spring Security应该使用哪个Filter对它进行处理。

2.CsrfToken的生成和存储

public class HttpSessionCsrfTokenRepository implements CsrfTokenRepository {
  ...
  public CsrfToken generateToken(HttpServletRequest request) {
    String token = UUID.randomUUID().toString();
    return new DefaultCsrfToken(this.headerName, this.parameterName, token);
  }
  ...
}

Spring Security提供了默认的CsrfTokenRepository——HttpSessionCsrfTokenRepository。在此对象中,generateToken方法会生成一个固定的CsrfToken,并通过DefaultCsrfToken类进行封装,然后将封装后的CsrfToken存储在HttpSession中。

3.前端获取CsrfToken

var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");

$(document).ajaxSend(function(e, xhr, options) {
  xhr.setRequestHeader(header, token);
});

为了获取服务器生成的CsrfToken,需要在前端页面中引入一个标签,标签的name属性为_csrf,在SpringSecurity中,会将CsrfToken存储到这个name属性对应的content中。

4.后端校验CsrfToken

public class CsrfFilter extends OncePerRequestFilter {
  ...
  protected void doFilterInternal(HttpServletRequest request,
      HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    ...
    CsrfToken csrfToken = this.csrfTokenRepository.loadToken(request);
    ...
    if (csrfToken == null) {
      if ("GET".equals(request.getMethod())) {
        ...
      } else {
        ...
        response.sendError(HttpServletResponse.SC_FORBIDDEN, "Missing CSRF token");
        return;
      }
    }
    ...
  }
  ...
}

在处理请求的Controller中,Spring Security会自动调用CsrfFilter过滤器来校验该请求中的CsrfToken与之前存储在HttpSession中的CsrfToken是否一致。如果不一致,则会给用户返回一个403的Not Accessible错误。

示例

示例1:前后端分离的Csrf防御

假设前后端分离的SpringBoot项目结构如下:

my-spring-boot
├── src
|   ├── main
|   |   ├── java
|   |   |   ├── com.example
|   |   |   |   ├── web
|   |   |   |   |   ├── ApiController.java
|   |   |   |   ├── MySpringBootApplication.java
|   |   ├── resources
|   |   |   ├── static
|   |   |   |   ├── csrf-example.html

对于这个场景,需要在前端页面生成CsrfToken,然后在Ajax中发送请求时携带CsrfToken。具体代码如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="_csrf" th:content="${_csrf.token}" />
    <meta name="_csrf_header" th:content="${_csrf.headerName}" />
    <title>CSRF Example</title>
  </head>
  <body>
    <h1>Spring Boot CSRF Example</h1>

    <button id="ajax-get-btn">GET Request</button>
    <button id="ajax-post-btn">POST Request</button>

    <script src="/webjars/jquery/jquery.min.js"></script>
    <script>
      $(function () {
        $('#ajax-get-btn').click(function() {
          $.ajax({
            url: '/api/hello?name=GET',
            type: 'GET',
            success: function(data) {
              $('#data').show();
              $('#data').text(data);
            }
          });
        });

        $('#ajax-post-btn').click(function() {
          $.ajax({
            url: '/api/hello?name=POST',
            type: 'POST',
            data: {},
            success: function(data) {
              $('#data').show();
              $('#data').text(data);
            }
          });
        });
      });
    </script>
    <div id="data"></div>
  </body>
</html>

在ApiController中,需要通过@PreAuthorize注解将请求限制为USER角色,防止不必要的请求。代码如下:

@RestController
@RequestMapping("/api")
public class ApiController {
  @GetMapping("/hello")
  @PreAuthorize("hasRole('USER')")
  public String helloGet() {
    return "hello GET";
  }

  @PostMapping("/hello")
  @PreAuthorize("hasRole('USER')")
  public String helloPost() {
    return "hello POST";
  }
}

示例2:Thymeleaf与SpringSecurity的Csrf防御

假设当前项目使用了Thymeleaf模板引擎,结构如下:

my-spring-boot
├── src
|   ├── main
|   |   ├── java
|   |   |   ├── com.example
|   |   |   |   ├── MySpringBootApplication.java
|   |   ├── resources
|   |   |   ├── templates
|   |   |   |   ├── csrf-example.html

对于这个场景,可以使用input标签的hidden类型来生成CsrfToken。具体代码如下:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="UTF-8" />
    <meta name="_csrf" th:content="${_csrf.token}" />
    <meta name="_csrf_header" th:content="${_csrf.headerName}" />
    <title>CSRF Example</title>
  </head>
  <body>
    <h1>Spring Boot CSRF Example</h1>
    <form action="#" th:action="@{/logout}" method="post">
      <input type="submit" value="Logout" />
      <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
    </form>
  </body>
</html>

需要注意的是,在模板引擎中必须使用SpringSecurity提供的ThymeleafDialect,此Dialect会帮助我们自动生成hidden类型的input标签。代码如下:

@Configuration
public class ThymeleafConfig {
  @Bean
  public SpringSecurityDialect securityDialect() {
    return new SpringSecurityDialect();
  }
}

通过在Thymeleaf模板中调用巨量其提供的<sec:csrfInput />标签即可自动生成CsrfToken:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
  <head>
    <meta charset="UTF-8" />
    <title>CSRF Example</title>
  </head>
  <body>
    <h1>Spring Boot CSRF Example</h1>
    <form action="#" th:action="@{/logout}" method="post">
      <input type="submit" value="Logout" />
      <sec:csrfInput />
    </form>
  </body>
</html>

结论

通过上述对SpringSecurity中防止Csrf攻击的实现代码解析,我们了解到SpringSecurity具有非常优秀的Csrf防御能力,同时也描述了如何在前后端分离的和Thymeleaf模板引擎的场景下使用CsrfToken。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringSecurity的防Csrf攻击实现代码解析 - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • jsp的九大内置对象深入讲解

    一、JSP九大内置对象 JSP的九大内置对象是指:1. request:封装客户端的请求,其中包含了与HTTP请求相关的信息,例如:请求参数、头信息等;2. response:封装服务器对客户端的响应,其中包含了HTTP响应本身以及向客户端发送的数据;3. pageContext:JSP页面上下文,包含了对该JSP页面的Servlet上下文、请求、响应等对象…

    Java 2023年6月15日
    00
  • 利用jsp+Extjs实现动态显示文件上传进度

    利用jsp+Extjs实现动态显示文件上传进度的完整攻略主要有以下几步: 1、前端页面 前端页面需要使用Extjs实现。首先需要在页面中引入相应的js文件,例如: <script src="ext-all.js"></script> <script src="ext-lang-zh_CN.js&qu…

    Java 2023年6月15日
    00
  • Java读取json数据并存入数据库的操作代码

    下面是Java读取Json数据并存入数据库的操作代码的攻略,包含以下四个步骤: 构建Json数据对象 读取Json数据 解析Json数据 将数据存入数据库 下面进行详细讲解。 步骤一:构建Json数据对象 使用Java构建Json对象可以使用json库的JSONObject类来构建。首先需要导入相应的依赖: <dependency> <gr…

    Java 2023年5月20日
    00
  • 一文带你掌握Java8中Lambda表达式 函数式接口及方法构造器数组的引用

    一文带你掌握Java8中Lambda表达式 函数式接口及方法构造器数组的引用 Lambda表达式 Lambda表达式是Java 8中引入的新特性之一,它是一个匿名函数,可以捕获参数并表现为一个代码块,而不像方法一样需要一个固定的名称。它主要用于传递行为或代码块以及事件处理等操作。 Lambda表达式的基本语法如下: (parameters) -> ex…

    Java 2023年5月26日
    00
  • Java 爬虫如何爬取需要登录的网站

    下面是我对Java爬虫如何爬取需要登录的网站的完整攻略: 一、背景介绍 有些网站需要用户登录后才能查看或获取相应数据,这对于一些需要批量获取数据的需求来说显得很麻烦。本文将介绍一种在Java中使用爬虫爬取需要登录的网站的方法,以及需要注意的一些细节。 二、分析 首先,我们需要了解需要登录的网站是如何实现用户认证,以及需要爬取的数据是如何在网站上呈现的。 一般…

    Java 2023年5月26日
    00
  • Springboot实现根据用户ID切换动态数据源

    下面详细讲解一下Spring Boot实现根据用户ID切换动态数据源的完整攻略。 1. 背景介绍 在一些需要多数据源分库分表的项目中,我们需要根据用户ID来动态切换数据源。比如将同一张表中不同用户的数据划分到不同的数据库中进行存储,这样可以有效地降低数据库的负载,提高系统的性能。 2. 实现步骤 2.1 引入相关依赖 我们可以通过引入Spring Boot的…

    Java 2023年6月3日
    00
  • Java中的Lambda表达式是什么?

    下面开始详细讲解Java中的Lambda表达式是什么? Lambda表达式简介 Lambda表达式是Java 8中引入的一种代码简化方式。它可以让我们更容易地编写函数式接口的实例。 Lambda表达式用于简化函数式接口的实现,其本质上是一种可传递的匿名函数:它没有名称,但它有参数列表、函数体和可能抛出的异常列表。 Lambda表达式的语法 Lambda表达式…

    Java 2023年4月27日
    00
  • JVM的内存分配及各种常量池的区别(静态常量池、运行时常量池、字符串常量池)

    JVM内存分配 先了解下JVM中的内存分配,此处以hotspot vm为例(官方jdk采用的vm) 程序计数器 栈 1. 虚拟机栈 2. 本地方法栈 Java堆 堆内存是各个线程共享的区域 方法区 它用于存储已经被虚拟机加载的类信息、常量、静态变量、即编译器编译后的代码等数据。静态变量、常量在方法区,所有方法,包括静态和非静态的,也在方法区 这里解释一下方法…

    Java 2023年4月17日
    00
合作推广
合作推广
分享本页
返回顶部