LeoniePhiline/axum-csrf-sync-pattern
GitHub: LeoniePhiline/axum-csrf-sync-pattern
为 Axum Web 框架提供基于同步令牌模式的 CSRF 防护中间件,解决跨站请求伪造问题。
Stars: 15 | Forks: 7
# Axum 同步令牌模式 CSRF 防护
此 crate 为 [axum](https://docs.rs/axum/) Web 框架提供跨站请求伪造(CSRF)防护层和中间件。
[](https://crates.io/crates/axum-csrf-sync-pattern)
[][docs]
[](https://github.com/LeoniePhiline/axum-csrf-sync-pattern/actions/workflows/ci.yml)
[](https://deps.rs/repo/github/LeoniePhiline/axum-csrf-sync-pattern)
该中间件实现了 [CSRF 同步令牌模式](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#synchronizer-token-pattern)
适用于通过 JavaScript 进行通信的 AJAX 后端和 API 端点,
遵循 OWASP CSRF 防护备忘单中的描述。
更多关于此 crate 的信息可以在 [crate 文档][docs] 中找到。
## 安装
```
axum-csrf-sync-pattern = "0.3.2"
```
## 示例
请参考 [示例项目](https://github.com/LeoniePhiline/axum-csrf-sync-pattern/tree/main/examples/) 了解同站和跨站使用情况。
同时也可以将 [crate 单元测试](https://github.com/LeoniePhiline/axum-csrf-sync-pattern/blob/main/src/lib.rs#:~:text=%23%5Bcfg,mod%20tests) 用作参考。
## 范围
此中间件通过 [自定义请求头](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#use-of-custom-request-headers) 实现令牌传输。
该中间件需要并构建在 [`axum_sessions`](https://docs.rs/axum-sessions/) 之上,
而后者又使用 [`async_session`](https://docs.rs/async-session/)。
当前版本适用于 `axum 0.6.x`、`axum-sessions 0.5.x` 和 `async_session 3.x`。
未来将支持 `axum 0.7` 及更高版本。
[同源策略](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) 阻止了外部脚本设置自定义请求头。
### 我应该在什么上下文中使用此中间件?
此中间件的目标是专门防止跨站请求伪造攻击,
适用于通过 JavaScript
[`fetch()` API](https://developer.mozilla.org/en-US/docs/Web/API/fetch) 或经典 [`XmlHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
与传统上所称的 “AJAX” 进行通信的应用。
同步令牌模式在 [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) 场景中特别有用,
因为底层会话 Cookie 被强制设为安全且无法被 JavaScript 访问,
而携带 CSRF 令牌的 HTTP 响应头可以通过 CORS [`Access-Control-Expose-Headers`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers) 响应头暴露。
尽管 [同源策略](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) 通常会阻止在跨域请求中设置自定义请求头,
但通过使用 [Access-Control-Allow-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers) CORS HTTP 响应头,
可以明确允许跨域请求携带所需的自定义 HTTP 请求头。
这种方式确保了由自动提交的表单或其他来自外部域的脚本所伪造的请求无法添加所需的头部。
### 何时应使用其他 CSRF 防护模式或库?
如果你计划在不使用 JavaScript 的情况下提交传统 HTML 表单,
并且不涉及跨域提交表单,
则应使用其他可用的中间件库。
## 安全性
### 令牌随机性
CSRF 令牌使用 [`rand::ThreadRng`](https://rust-random.github.io/rand/rand/rngs/struct.ThreadRng.html) 生成,
这被认为是加密安全的(CSPRNG)。
有关更多信息,请参考 “[Our RNGs](https://rust-random.github.io/book/guide-rngs.html#cryptographically-secure-pseudo-random-number-generators-csprngs)”。
### 底层会话安全性
会话安全性是至关重要的——所应用的 CSRF 防护方法的安全性
取决于承载服务端令牌的会话的安全程度。
- 在创建 [SessionLayer](https://docs.rs/axum-sessions/latest/axum_sessions/struct.SessionLayer.html) 时,请确保使用至少 64 字节的加密安全随机性。
- 不要降低安全默认值:请保持会话 Cookie 的 `secure` 标志 **开启**。
- 使用尽可能严格的同站策略。
### CORS 安全性
如果需要提供并保护跨域请求:
- 仅允许配置 [`CorsLayer`](https://docs.rs/tower-http/latest/tower_http/cors/struct.CorsLayer.html) 时的后端源。
- 仅允许所需的请求头。(至少包含 CSRF 请求令牌头。)
- 仅暴露所需的响应头。(至少包含 CSRF 响应令牌头。)
### 错误详情不泄露
错误通过 [`tracing::error!`] 记录。
错误响应不包含错误详情。
可使用 [`tower_http::TraceLayer`](https://docs.rs/tower-http/latest/tower_http/trace/struct.TraceLayer.html) 捕获并查看跟踪信息。
## 安全性说明
此 crate 不使用任何 `unsafe` 代码。
层与中间件功能已通过测试。
请查看模块源代码以了解更多信息。
## 用法
请参考 [示例项目](https://github.com/LeoniePhiline/axum-csrf-sync-pattern/tree/main/examples/) 了解同站和跨站使用情况。
这些示例为交互式演示。运行后,可在浏览器中交互使用。
### 同站使用
**注意:** crate 仓库中包含同站和跨站使用的示例项目!
在每个示例目录中,执行 `cargo run`,然后在浏览器中打开 [http://127.0.0.1:3000](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({
/* ... */
}),
});
};
```
如需完整演示,请运行 [同站示例项目](https://github.com/LeoniePhiline/axum-csrf-sync-pattern/tree/main/examples/same-site)。
你可以在 [http://127.0.0.1:3000](http://127.0.0.1:3000) 找到交互式演示。
### 启用 CORS 的使用
**注意:** crate 仓库中包含同站和跨站使用的示例项目!
在每个示例目录中,执行 `cargo run`,然后在浏览器中打开 [http://127.0.0.1:3000](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({
/* ... */
}),
});
};
```
如需完整演示,请运行 [跨站示例项目](https://github.com/LeoniePhiline/axum-csrf-sync-pattern/tree/main/examples/cross-site)。
你可以在 [http://127.0.0.1:3000](http://127.0.0.1:3000) 找到交互式演示。
## 贡献
欢迎提交拉取请求!
标签:API安全, axum-sessions, Axum中间件, CSRF防护, HTTP安全, JSON输出, Rust, Rust库, Web安全, 中间件开发, 令牌验证, 前端安全, 可视化界面, 同步令牌模式, 异步Web框架, 网络流量审计, 蓝队分析, 请求头验证, 跨站请求伪造, 通知系统