前端开发入门到精通的在线学习网站

网站首页 > 资源文章 正文

Spring Security 应用 CSRF 保护和 CORS(2)

qiguaw 2024-09-21 21:42:19 资源文章 21 ℃ 0 评论


1.2 实际场景中使用CSRF保护

在本节中,我们将讨论在实际情况下如何应用 CSRF 保护。既然您已经了解了 CSRF 保护在 Spring Security 中的工作方式,那么您需要知道在现实世界中应该在何处使用它。哪些应用程序需要使用 CSRF 保护?

你使用 CSRF 保护 web 应用程序在浏览器中运行,你应该期待,变异操作可以通过浏览器加载显示应用程序的内容。我可以提供最基本的例子是一个简单的 web 应用程序开发标准 Spring MVC 流。在前面文章中讨论表单登录时,我们已经做了这样一个应用程序,而这个 web 应用程序实际上使用了 CSRF 保护。您是否注意到该应用程序中的登录操作使用了 HTTP POST ? 那么为什么在这种情况下我们不需要明确地做任何关于 CSRF 的事情呢?我们之所以没有观察到这一点是因为我们自己并没有在它内部发展出任何变异操作。

对于默认登录,Spring Security 正确地为我们应用了 CSRF 保护。框架负责将 CSRF 令牌添加到登录请求中。现在,让我们开发一个类似的应用程序,进一步了解 CSRF 保护是如何工作的。如图 5 所示,在本节中我们

  • 构建一个带有登录表单的 web 应用程序示例
  • 看看默认的登录实现如何使用CSRF令牌
  • 在主页实现一个HTTP POST调用


这个计划。在本节中,我们首先构建并分析一个简单的应用程序,以理解 Spring Security 如何应用 CSRF 保护,然后编写自己的 POST 调用。

在这个示例应用程序中,您将注意到,在正确使用 CSRF 令牌之前,HTTP POST 调用无法工作,并且您将学习如何在这样的 web 页面的表单中应用 CSRF 令牌。要实现这个应用程序,我们首先创建一个新的 Spring Boot 项目。下一个代码片段展示了所需的依赖关系:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>

然后,当然,我们需要配置表单登录和至少一个用户。下面的清单展示了配置类,它定义了UserDetailsService,添加了一个用户,并配置了 formLogin 方法。

清单 4 配置类的定义

public class ProjectConfig 
  extends WebSecurityConfigurerAdapter {

    // 添加管理一个用户的 UserDetailsService bean来测试应用程序
  @Bean
  public UserDetailsService uds() {
    var uds = new InMemoryUserDetailsManager();

    var u1 = User.withUsername("mary")
                 .password("12345")
                 .authorities("READ")
                 .build();

    uds.createUser(u1);
    return uds;
  }

    //添加 PasswordEncoder
  @Bean
  public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
  }

    //重写 configure() 以设置表单登录身份认证方法,并指定只有经过身份认证的用户才能访问任何端点
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
          .anyRequest().authenticated();

    http.formLogin()
        .defaultSuccessUrl("/main", true);
  }
}

我们在名为 controllers 的包中以及 Maven 项目的 resources/templates 文件夹的 main.html 文件中为主页添加了一个控制器类。 该 main.html 文件暂时可以保持空白,因为在首次执行该应用程序时,我们仅关注登录页面如何使用 CSRF 令牌。 下面的清单展示了 MainController 类,该类服务于主页。

清单 5 MainController 类的定义

@Controller
public class MainController {

  @GetMapping("/main")
  public String main() {
    return "main.html";
  }
}

运行该应用程序后,您可以访问默认登录页面。 如果使用浏览器的检查元素功能检查表单,则可以观察到登录表单的默认实现会发送 CSRF 令牌。 这就是即使您使用 HTTP POST 请求,您的登录也可以启用 CSRF 保护的原因! 图 6 显示了登录表单如何通过隐藏输入发送 CSRF 令牌。


默认表单登录使用隐藏输入在请求中发送 CSRF 令牌。这就是为什么使用 HTTP POST 方法的登录请求可以在启用 CSRF 保护的情况下工作。

但是如何开发使用 POST、PUT 或 DELETE 作为 HTTP 方法的自己的端点呢?对于这些,如果启用了 CSRF 保护,我们必须注意发送 CSRF 令牌的值。为了测试这一点,让我们使用 HTTP POST 向我们的应用程序添加一个端点。我们从主页调用这个端点,并为此创建第二个控制器,称为 ProductController 。在这个控制器中,我们定义了一个端点 /product/add,它使用 HTTP POST。此外,我们使用主页上的一个表单来调用这个端点。下一个清单定义了 ProductController 类。

清单 6 ProductController 类的定义

@Controller
@RequestMapping("/product")
public class ProductController {

  private Logger logger =
          Logger.getLogger(ProductController.class.getName());

  @PostMapping("/add")
  public String add(@RequestParam String name) {
    logger.info("Adding product " + name);
    return "main.html";
  }
}

端点接收请求参数并将其打印到应用程序控制台中。下面的清单显示了 main.html 文件中定义的表单的定义。

清单 7 main.html 页面中的表单定义

<form action="/product/add" method="post">
   <span>Name:</span>
   <span><input type="text" name="name" /></span>
   <span><button type="submit">Add</button></span>
</form>

现在可以重新运行应用程序并测试表单。您将看到,当提交请求时,将显示一个默认的错误页面,该页面确认来自服务器的响应上的 HTTP 403 Forbidden 状态 ( 图 7 )。HTTP 403 禁用状态的原因是缺少 CSRF 令牌。


如果不发送 CSRF 令牌,服务器将不会接受使用 HTTP POST 方法完成的请求。应用程序将用户重定向到一个默认错误页面,该页面确认响应上的状态是 HTTP 403 Forbidden。


要解决这个问题并让服务器允许请求,我们需要在通过表单完成的请求中添加 CSRF 令牌。一种简单的方法是使用隐藏的输入组件,就像您在默认表单登录中看到的那样。您可以实现如下清单所示。

清单 8 通过表单将 CSRF 令牌添加到请求中

<form action="/product/add" method="post">
   <span>Name:</span>
   <span><input type="text" name="name" /></span>
   <span><button type="submit">Add</button></span>

   <input type="hidden"
          th:name="${_csrf.parameterName}"
          th:value="${_csrf.token}" />
</form>

注意

在本例中,我们使用 Thymeleaf,因为它提供了一种获取视图中请求属性值的简单方法。在本例中,我们需要打印 CSRF 令牌。请记住,CsrfFilter 将令牌的值添加到请求的 _csrf 属性中。对 Thymeleaf 不是强制的。您可以使用任何选择来将令牌值打印到响应。

重新运行应用程序后,可以再次测试表单。这一次,服务器接受请求,应用程序在控制台中打印日志行,证明执行成功。此外,如果检查表单,可以找到具有 CSRF 令牌值的隐藏输入 ( 图 8 ) 。


主页上定义的表单现在将在请求中发送 CSRF 令牌的值。通过这种方式,服务器允许请求并执行控制器操作。在页面的源代码中,您现在可以找到表单用于在请求中发送 CSRF 令牌的隐藏输入。

提交表单后,您应该在应用程序控制台中发现类似于下面的一行:

INFO 20892 --- [nio-8080-exec-7] c.l.s.controllers.ProductController    : Adding product Chocolate

当然,对于页面用于调用可变操作的任何操作或异步 JavaScript 请求,都需要发送有效的 CSRF 令牌。这是应用程序用来确保请求不是来自第三方的最常用方法。第三方请求可以尝试模拟用户以他们的名义执行操作。

CSRF 令牌在由同一台服务器同时负责前端和后端的架构中工作得很好,主要是因为它的简单。但是,当客户端独立于它使用的后端解决方案时,CSRF 令牌不能很好地工作。当你有一个移动应用作为客户端或一个独立开发的 web前端时,这种情况就会发生。用 Angular、ReactJS 或 Vue.js 这样的框架开发的 web 客户端在 web 应用架构中无处不在,这也是为什么你需要知道如何为这些情况实现安全方法的原因。我们将在后面文章中讨论这些类型的设计:

这看起来可能是一个微不足道的错误,但根据我的经验,我在应用程序中看到过太多这样的情况——永远不要在修改操作时使用 HTTP GET !不要实现更改数据并允许使用 HTTP GET端点调用数据的行为。记住,调用 HTTP GET 端点不需要 CSRF 令牌。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表