6.1 CSRF 防护
跨站请求伪造(CSRF)是一种网络攻击方式,攻击者诱使用户在不知情的情况下,通过用户已登录的Web应用发送恶意请求。幸运的是,通过适当的防护措施,可以有效防止这类攻击。
6.1.1 基础知识详解
跨站请求伪造(CSRF)是一种常见的网络攻击,它利用了Web应用中用户会话的自动化完成机制。在不知情的情况下,攻击者可以诱导用户执行非预期的操作,如更改密码、转账等。了解CSRF的基本概念和防护机制对于开发安全的Web应用至关重要。
CSRF 攻击原理
- 用户登录:用户在某个Web应用中登录,并在其浏览器中保持了登录状态(通常是通过Cookies)。
- 恶意请求:用户访问了一个恶意网站,该网站包含了一个指向上述Web应用的恶意请求(例如,一个隐藏的表单或者一段JavaScript代码)。
- 自动提交:由于用户的浏览器仍然保持着对目标Web应用的登录状态,所以恶意请求会自动携带用户的会话Cookies并被提交。
- 非预期操作:如果目标Web应用没有适当的防护机制,恶意请求就可能以用户的身份执行非预期的操作。
CSRF 防护机制
- CSRF 令牌:最常见的防护机制是使用CSRF令牌(也称为anti-CSRF令牌)。每次用户发起请求时,服务器会生成一个唯一的、不可预测的令牌,并在响应中返回给用户(通常是作为表单的一部分或通过HTTP头发送)。然后,用户在后续请求中必须提交这个令牌,服务器将验证令牌的有效性。
- 双重提交Cookies:这种方法不需要在服务器端存储CSRF令牌。它假定攻击者无法读取或设置目标站点的Cookies。令牌存储在一个Cookie中,并且通过请求(如表单字段)再次提交。服务器验证Cookie中的令牌和请求中的令牌是否匹配。
- 自定义请求头:由于跨站请求通常无法设置自定义请求头,因此检查HTTP请求中是否存在自定义头(如
X-Requested-With
)也是一种简单有效的防护手段。 - Referer验证:检查HTTP请求的
Referer
头部可以帮助确定请求是否来自于信任的来源。然而,由于隐私考虑,一些用户或浏览器可能会禁用或篡改Referer
头部,限制了这种方法的有效性。
最佳实践
- 为所有表单和状态改变请求使用CSRF令牌:确保每个执行状态改变的请求(如POST、PUT、DELETE等)都需要一个有效的CSRF令牌。
- 使用框架提供的CSRF防护:许多Web框架(如Spring Security)提供了内置的CSRF防护支持,开发者应该充分利用这些特性来保护应用。
- 定期旋转CSRF令牌:定期更换CSRF令牌可以减少令牌被猜测或泄露的风险。
- 为CSRF令牌设置适当的作用域:令牌应该是特定于用户会话的,并且应该限制其在应用中的作用域,以避免跨站点的泄露风险。
通过理解CSRF攻击的工作原理和实施有效的防护措施,开发者可以显著提高Web应用的安全性,保护用户免受这种攻击方式的影响。
6.1.2 重点案例:Spring Security 中的 CSRF 防护
Spring Security 提供了一套全面的 CSRF 防护机制,确保应用安全地处理每一个请求。通过实用的案例,我们将一步步探索如何在 Spring Boot 应用中利用 Spring Security 实现 CSRF 防护。
案例 Demo
假设我们正在开发一个简单的博客平台,用户需要登录后才能发布文章。以下是如何通过 Spring Security 添加 CSRF 防护的步骤。
步骤 1: 启用 Spring Security
首先,确保你的 Spring Boot 应用中已经添加了 Spring Security 依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
步骤 2: 配置 CSRF 保护
在 Spring Security 配置中,CSRF 保护默认是启用的。但是,为了展示如何显式启用 CSRF 保护,并演示如何配置,我们将在 WebSecurityConfig
类中添加相关配置。
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // 其他配置... .csrf().enable(); // 显式启用 CSRF 保护 } }
步骤 3: 在表单中包含 CSRF 令牌
对于每个需要 CSRF 保护的表单,Spring Security 需要你在表单中包含一个 CSRF 令牌。如果你使用的是 Thymeleaf,它会自动为你的表单添加 CSRF 令牌。
<form action="/postArticle" method="post"> <input type="text" name="title" /> <input type="text" name="content" /> <!-- Thymeleaf 自动添加 CSRF 令牌 --> <button type="submit">发布文章</button> </form>
步骤 4: 处理 AJAX 请求中的 CSRF 令牌
对于 AJAX 请求,你需要手动将 CSRF 令牌添加到请求的头部。
首先,从页面中获取 CSRF 令牌(假设你已经将其存储在了一个 meta 标签中)。
<meta name="_csrf" content="${_csrf.token}"/> <meta name="_csrf_header" content="${_csrf.headerName}"/>
然后,在发送 AJAX 请求时,从 meta 标签中读取并添加 CSRF 令牌。
const csrfToken = document.querySelector('meta[name="_csrf"]').getAttribute('content'); const csrfHeader = document.querySelector('meta[name="_csrf_header"]').getAttribute('content'); fetch('/postArticle', { method: 'POST', headers: { 'Content-Type': 'application/json', [csrfHeader]: csrfToken // 添加 CSRF 令牌到请求头 }, body: JSON.stringify({title: '新文章', content: '文章内容'}) });
测试 CSRF 防护
启动应用并尝试发布文章。你会发现,只有当请求中包含有效的 CSRF 令牌时,文章才能成功发布。
通过这个案例,你可以看到 Spring Security 如何帮助我们简单有效地实现 CSRF 防护,从而保护应用免受跨站请求伪造攻击。无论是传统的表单提交还是现代的 AJAX 请求,Spring Security 都为我们提供了强大的工具和机制来确保每个请求的安全。
6.1.3 拓展案例 1:自定义 CSRF 令牌仓库
在某些场景下,应用可能需要更灵活地处理 CSRF 令牌,比如在分布式系统中共享 CSRF 令牌或在客户端和服务器之间以不同的方式传递令牌。这时,通过实现自定义 CSRF 令牌仓库(CsrfTokenRepository
)来满足这些特定需求就显得尤为重要。
案例 Demo
假设我们的应用需要将 CSRF 令牌存储在 Redis 中,以支持在多个实例间共享令牌。以下是如何实现一个自定义的 CSRF 令牌仓库的步骤。
步骤 1: 实现 CsrfTokenRepository
首先,创建一个新的类 RedisCsrfTokenRepository
实现 CsrfTokenRepository
接口。这个类将负责从 Redis 获取和存储 CSRF 令牌。
public class RedisCsrfTokenRepository implements CsrfTokenRepository { private final RedisTemplate<String, CsrfToken> redisTemplate; public RedisCsrfTokenRepository(RedisTemplate<String, CsrfToken> redisTemplate) { this.redisTemplate = redisTemplate; } @Override public CsrfToken generateToken(HttpServletRequest request) { String tokenId = UUID.randomUUID().toString(); return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", tokenId); } @Override public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) { String key = resolveSessionKey(request); if (token == null) { redisTemplate.delete(key); } else { redisTemplate.opsForValue().set(key, token, 30, TimeUnit.MINUTES); } } @Override public CsrfToken loadToken(HttpServletRequest request) { String key = resolveSessionKey(request); return redisTemplate.opsForValue().get(key); } private String resolveSessionKey(HttpServletRequest request) { return "csrf:" + request.getSession().getId(); } }
注意:在实际应用中,你需要配置 RedisTemplate
以正确序列化和反序列化 CsrfToken
对象。
步骤 2: 配置 Spring Security 使用自定义 CSRF 令牌仓库
接下来,在你的 Spring Security 配置中指定应用使用 RedisCsrfTokenRepository
作为 CSRF 令牌仓库。
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private RedisTemplate<String, CsrfToken> redisTemplate; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(new RedisCsrfTokenRepository(redisTemplate)); } }
通过这种方式,应用的 CSRF 令牌将被存储在 Redis 中,支持在多个应用实例间共享令牌,这对于构建可扩展的分布式应用尤为重要。
测试自定义 CSRF 令牌仓库
现在,当用户访问应用并生成 CSRF 令牌时,这些令牌将被存储在 Redis 中。无论用户是通过哪个实例发起请求,应用都能从 Redis 中检索到相应的 CSRF 令牌,并进行验证。
通过实现自定义的 CSRF 令牌仓库,你可以根据应用的具体需求灵活地处理 CSRF 令牌,无论是在客户端和服务器之间自定义传递令牌的方式,还是在分布式系统中共享令牌,都能提供一个安全可靠的解决方案。
6.1.4 拓展案例 2:禁用特定请求的 CSRF 防护
虽然 CSRF 防护是保护 Web 应用安全的重要机制,但在某些情况下,你可能需要为特定的请求路径禁用 CSRF 防护。例如,对于某些第三方服务的回调端点或是性能敏感的 API,可能不适合执行 CSRF 检查。Spring Security 提供了灵活的配置选项,允许你根据需要调整 CSRF 防护的范围。
案例 Demo
假设我们有一个应用,其中包含一个第三方支付服务的回调端点 /payment/notify
,由于这个端点是由支付服务提供商直接调用的,用户不会直接与之交互,因此我们可以安全地为这个特定路径禁用 CSRF 防护。
步骤 1: 配置 Spring Security
在 Spring Security 的配置类中,使用 ignoringAntMatchers()
方法指定需要禁用 CSRF 防护的路径。
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .ignoringAntMatchers("/payment/notify") // 禁用 /payment/notify 端点的 CSRF 防护 .and() .authorizeRequests() .antMatchers("/payment/notify").permitAll() // 允许所有用户无需认证即可访问 .anyRequest().authenticated(); // 其他所有请求都需要认证 } }
通过这种配置,/payment/notify
端点将不再要求请求中包含 CSRF 令牌,同时允许未经认证的用户访问,确保支付服务的回调请求能够成功到达并被处理。
步骤 2: 处理回调请求
接下来,实现一个控制器来处理 /payment/notify
端点的请求。
@RestController public class PaymentController { @PostMapping("/payment/notify") public ResponseEntity<String> paymentNotification(@RequestBody PaymentNotification notification) { // 处理支付通知逻辑 return ResponseEntity.ok("Payment processed"); } }
测试禁用 CSRF 防护
启动应用后,尝试向 /payment/notify
端点发送一个 POST 请求,即使请求中不包含 CSRF 令牌,请求也应该成功被接受和处理。
通过在特定场景下禁用 CSRF 防护,你可以为需要与外部系统交互的端点提供灵活性,同时仍然保持应用的安全性。重要的是,这种配置应谨慎使用,确保不会意外地为敏感操作禁用了 CSRF 防护,从而降低安全风险。
通过这些案例,你可以看到 CSRF 防护是网络安全中的重要组成部分,Spring Security 提供了强大而灵活的机制来保护应用免受 CSRF 攻击。正确实施 CSRF 防护措施,可以有效提升应用的安全性。
6.2 跨域请求处理(CORS)
在构建现代 Web 应用时,跨域资源共享(CORS)是一个常见的挑战。CORS 是一种机制,它允许限制的资源(如字体、JavaScript 等)在一个网页应用被另一个不同源(域名、协议或端口)的网页请求时,如何被请求。理解和合理配置 CORS 对于保护 Web 应用、提供灵活的服务消费体验至关重要。
6.2.1 基础知识详解
跨域资源共享(CORS)是一个 W3C 标准,允许网页脚本能够向不同源(域、协议或端口)的服务器发出请求,从而克服了 AJAX 直接由浏览器同源政策所施加的限制。在深入探讨如何在应用中实现和配置 CORS 之前,了解其基础知识和工作原理是非常重要的。
同源政策
- 定义:同源政策是一种约定,它限制了一个源的文档或脚本如何与另一个源的资源进行交互。这是一个重要的安全机制,用于隔离潜在恶意文件。
- 源的定义:源是由协议、域名和端口三部分组成的。只有当这三部分完全匹配时,两个 URL 才属于同一个源。
CORS 工作原理
- 简单请求:
- 满足特定条件的请求被视为简单请求(例如使用 GET、HEAD 或 POST 方法,且 HTTP 头部限于一组特定集合)。
- 浏览器直接发出简单请求,但会在请求头中添加
Origin
字段,服务器根据这个字段决定是否允许该跨域请求。
- 预检请求(Preflight):
- 不符合简单请求条件的请求,浏览器会先发送一个预检请求,使用 OPTIONS 方法,询问服务器是否允许该跨域请求。
- 预检请求的响应中,服务器可以指定允许的方法、头部信息和是否允许携带凭证等信息。
CORS 响应头
- Access-Control-Allow-Origin:指定哪些网站可以参与跨域资源共享。
- Access-Control-Allow-Methods:指明在实际请求中允许使用的 HTTP 方法。
- Access-Control-Allow-Headers:指明实际请求中允许携带的首部字段。
- Access-Control-Allow-Credentials:指明是否允许发送 Cookie。
- Access-Control-Max-Age:指明预检请求的结果能够被缓存多久。
实现 CORS 支持
- 服务器端配置:服务器需要正确处理和响应跨域请求,包括处理预检请求并在响应中设置适当的 CORS 相关头部。
- 客户端支持:在发起跨域 AJAX 请求时,客户端(如 Web 浏览器)将自动处理 CORS 流程,开发者需要确保请求符合 CORS 要求,可能需要设置特定的请求头。
安全考虑
- 精确配置:
Access-Control-Allow-Origin
不应该设置为*
(表示接受任意域的请求),除非资源完全公开。对于需要凭证的请求,这个值必须是请求页面的完整源,不可以使用*
。 - 限制方法和头部:仅允许应用所需的方法和头部,减少潜在的攻击面。
CORS 的正确实现和配置对于保护 Web 应用的安全、提供跨域服务至关重要。理解其基础知识有助于开发者在保障安全的同时,实现应用的灵活访问和资源共享。
6.2.2 重点案例:在 Spring Boot 应用中配置 CORS
在这个案例中,我们将通过一个 Spring Boot 应用来展示如何灵活地配置 CORS,使得前端应用能够安全地跨域请求后端资源。我们假设后端API托管在 https://api.example.com
,而前端应用运行在 https://app.example.com
。
案例 Demo
步骤 1: 全局 CORS 配置
全局配置是一种简单且通用的方式来设置 CORS,适用于整个 Spring Boot 应用。在 WebSecurityConfig
类中通过重写 configure(HttpSecurity http)
方法实现。
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http // 其他安全配置... .cors(cors -> cors .configurationSource(request -> { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of("https://app.example.com")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE")); config.setAllowedHeaders(List.of("Content-Type", "Authorization")); config.setAllowCredentials(true); return config; }) ); } }
这段代码中,我们为来自 https://app.example.com
的请求配置了 CORS,允许了常用的 HTTP 方法,并允许携带认证信息(如 Cookies 和 HTTP 认证)。
步骤 2: 控制器级别的 CORS 配置
如果你想对特定的控制器或请求映射进行更细粒度的 CORS 配置,可以使用 @CrossOrigin
注解。
@RestController @RequestMapping("/api") public class DataController { @CrossOrigin(origins = "https://app.example.com", methods = {RequestMethod.GET, RequestMethod.POST}, maxAge = 3600) @GetMapping("/data") public ResponseEntity<String> getData() { return ResponseEntity.ok("Data from backend"); } }
在这个例子中,/api/data
端点专门为 https://app.example.com
配置了 CORS,允许 GET 和 POST 请求,并设置了预检请求的缓存时间为 3600 秒。
步骤 3: 使用 WebMvcConfigurer
自定义 CORS 配置
对于需要更复杂的 CORS 配置,例如根据请求动态决定允许的源,可以实现 WebMvcConfigurer
接口并重写 addCorsMappings
方法。
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("https://app.example.com") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("Content-Type", "Authorization") .allowCredentials(true) .maxAge(3600); } }
这种方法提供了对 CORS 配置全面的控制,允许你针对不同的路径模式定义不同的策略。
测试 CORS 配置
启动你的 Spring Boot 应用后,可以使用前端 JavaScript 代码或者 Postman 发起跨域请求测试 CORS 配置是否生效。例如,从 https://app.example.com
页面上发起对 https://api.example.com/api/data
的 AJAX 请求,应该能够成功获取数据而不会遇到 CORS 错误。
通过上述案例,你可以看到在 Spring Boot 应用中配置 CORS 的多种方式。这些配置确保了后端服务能够安全地响应来自不同源的前端请求,同时保持了应用的灵活性和安全性。
第6章 Spring Security 的 Web 安全性(2024 最新版)(下)+https://developer.aliyun.com/article/1487153