#csrf #axum #middleware #security #web-framework #api-request #http-request

axum-csrf-sync-pattern

这是一个 axum 层和中间件,通过实现 OWASP 描述的 CSRF 同步令牌模式,为同站和跨站 API 请求提供跨站请求伪造保护。

10 个版本

0.3.2 2024 年 4 月 5 日
0.3.1 2023 年 4 月 21 日
0.2.2 2022 年 12 月 1 日
0.2.0 2022 年 11 月 29 日
0.1.4 2022 年 11 月 29 日

#303HTTP 服务器

Download history 5/week @ 2024-04-12 3/week @ 2024-05-17 1/week @ 2024-05-24

468 每月下载量

MPL-2.0 许可证

48KB
495

Axum 同步令牌模式 CSRF 防护

这个 crate 为与 axum 网络框架一起使用提供了跨站请求伪造保护层和中间件。

Crates.io Documentation Continuous integration Dependency status

该中间件实现了 OWASP CSRF 防护技巧表中描述的 CSRF 同步令牌模式,用于 AJAX 后端和 API 端点。

有关此 crate 的更多信息,请参阅 crate 文档

安装

axum-csrf-sync-pattern = "0.3.2"

示例

请参阅 示例项目 以了解同站和跨站使用。

还可以使用 crate 单元测试 作为参考。

范围

此中间件通过 自定义请求头 实现令牌传输。

此中间件需要基于 axum_sessions 构建,而 axum_sessions 又使用 async_session

当前版本适用于并支持 axum 0.6.xaxum-sessions 0.5.xasync_session 3.x

将支持 axum 0.7 及更高版本。

同源策略(Same Origin Policy)防止外部脚本设置自定义请求头。

在什么情况下我应该使用这个中间件?

该中间件的目标是防止在通过JavaScript的fetch API或传统的XmlHttpRequest(通常称为“AJAX”)与后端通信的应用程序中发生跨站请求伪造攻击。

同步令牌模式在CORS上下文中特别有用,因为底层的会话cookie必须被安全保护,并且无法通过JavaScript访问,而携带CSRF令牌的自定义HTTP响应头可以通过CORS的Access-Control-Expose-Headers HTTP响应头来公开。

虽然同源策略通常防止在跨源请求上设置自定义请求头,但可以使用Access-Control-Allow-Headers CORS HTTP响应头来专门允许CORS请求携带所需的自定义HTTP请求头。

这种方法确保来自外部源的自提交表单或其他数据提交脚本无法添加所需的头。

我应该什么时候使用其他CSRF保护模式或库?

如果您计划提交不带JavaScript的经典HTML表单,并且不将表单数据发送到其他源,请使用其他可用的中间件库。

安全性

令牌随机性

CSRF令牌是使用rand::ThreadRng生成的,这是一种被认为是密码学安全的(CSPRNG)。更多信息请见"我们的RNGs"。

底层会话安全性

底层会话的安全性至关重要——应用的CSRF预防方法的安全性只能与携带服务器端令牌的会话相当。

  • 在创建您的SessionLayer时,请确保使用至少64字节的密码学安全随机数。
  • 不要降低安全默认值:保持会话cookie的secure标志开启。
  • 使用最严格的same-site策略。

CORS安全性

如果您需要提供并保护跨站请求

  • 在配置CorsLayer时,仅允许您的后端源。
  • 仅允许您需要的头。 (至少是CSRF请求令牌头。)
  • 仅公开您需要的头。 (至少是CSRF响应令牌头。)

不泄露错误细节

错误使用tracing::error!记录。错误响应不包含错误详情。

使用 tower_http::TraceLayer 来捕获和查看追踪信息。

安全性

此crate不使用任何unsafe代码。

已测试了层和中间件的功能。查看模块源代码以获取更多信息。

使用方法

请查看示例项目以了解同站和跨站使用情况。这些示例是交互式演示。运行它们,然后在浏览器中与之交互。

同站使用

注意:crate仓库包含同站和跨站使用的示例项目!在每个示例目录中,执行cargo run,然后在您的浏览器中打开http://127.0.0.1:3000

在您的后端应用程序中配置会话和CSRF保护层

use axum::{
    body::Body,
    http::StatusCode,
    routing::{get, Router},
};
use axum_csrf_sync_pattern::{CsrfLayer, RegenerateToken};
use axum_sessions::{async_session::MemoryStore, SessionLayer};
use rand::RngCore;

let mut secret = [0; 64];
rand::thread_rng().try_fill_bytes(&mut secret).unwrap();

async fn handler() -> StatusCode {
    StatusCode::OK
}

let app = Router::new()
 .route("/", get(handler).post(handler))
 .layer(
     CsrfLayer::new()

     // Optionally, configure the layer with the following options:

     // Default: RegenerateToken::PerSession
     .regenerate(RegenerateToken::PerUse)
     // Default: "X-CSRF-TOKEN"
     .request_header("X-Custom-Request-Header")
     // Default: "X-CSRF-TOKEN"
     .response_header("X-Custom-Response-Header")
     // Default: "_csrf_token"
     .session_key("_custom_session_key")
 )
 .layer(SessionLayer::new(MemoryStore::new(), &secret));

// Use hyper to run `app` as service and expose on a local port or socket.

接收令牌并使用您自定义的头部发送同站请求

const test = async () => {
  // Receive CSRF token (Default response header name: 'X-CSRF-TOKEN')
  const token = (await fetch("/")).headers.get("X-Custom-Response-Header");

  // Submit data using the token
  await fetch("/", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      // Default request header name: 'X-CSRF-TOKEN'
      "X-Custom-Request-Header": token,
    },
    body: JSON.stringify({
      /* ... */
    }),
  });
};

运行同站示例项目进行完整演示。您将在http://127.0.0.1:3000找到交互式演示。

CORS启用使用

注意:crate仓库包含同站和跨站使用的示例项目!在每个示例目录中,执行cargo run,然后在您的浏览器中打开http://127.0.0.1:3000

在您的后端应用程序中配置CORS层、会话和CSRF保护层

use axum::{
    body::Body,
    http::{header, Method, StatusCode},
    routing::{get, Router},
};
use axum_csrf_sync_pattern::{CsrfLayer, RegenerateToken};
use axum_sessions::{async_session::MemoryStore, SessionLayer};
use rand::RngCore;
use tower_http::cors::{AllowOrigin, CorsLayer};

let mut secret = [0; 64];
rand::thread_rng().try_fill_bytes(&mut secret).unwrap();

async fn handler() -> StatusCode {
    StatusCode::OK
}

let app = Router::new()
 .route("/", get(handler).post(handler))
 .layer(
     // See example above for custom layer configuration.
     CsrfLayer::new()
 )
 .layer(SessionLayer::new(MemoryStore::new(), &secret))
 .layer(
     CorsLayer::new()
         .allow_origin(AllowOrigin::list(["https://www.example.com".parse().unwrap()]))
         .allow_methods([Method::GET, Method::POST])
         .allow_headers([header::CONTENT_TYPE, "X-CSRF-TOKEN".parse().unwrap()])
         .allow_credentials(true)
         .expose_headers(["X-CSRF-TOKEN".parse().unwrap()]),
);

// Use hyper to run `app` as service and expose on a local port or socket.

接收令牌并使用您自定义的头部发送跨站请求

const test = async () => {
  // Receive CSRF token
  const token = (
    await fetch("https://backend.example.com/", {
      credentials: "include",
    })
  ).headers.get("X-CSRF-TOKEN");

  // Submit data using the token
  await fetch("https://backend.example.com/", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-CSRF-TOKEN": token,
    },
    credentials: "include",
    body: JSON.stringify({
      /* ... */
    }),
  });
};

运行跨站示例项目进行完整演示。您将在http://127.0.0.1:3000找到交互式演示。

贡献

欢迎提交拉取请求!

依赖

~12–20MB
~279K SLoC