#axum #csrf #web-apps #cookies

axum_csrf

提供CSRF(跨站请求伪造)保护层的库

19个不稳定版本 (8个破坏性更新)

0.9.0 2023年11月27日
0.7.2 2023年7月13日
0.6.2 2023年3月31日
0.6.0 2022年11月26日
0.1.1 2021年11月24日

#123 in HTTP客户端

Download history 46/week @ 2024-04-22 64/week @ 2024-04-29 63/week @ 2024-05-06 57/week @ 2024-05-13 80/week @ 2024-05-20 62/week @ 2024-05-27 46/week @ 2024-06-03 68/week @ 2024-06-10 37/week @ 2024-06-17 30/week @ 2024-06-24 9/week @ 2024-07-01 19/week @ 2024-07-08 20/week @ 2024-07-15 39/week @ 2024-07-22 52/week @ 2024-07-29 45/week @ 2024-08-05

每月160次下载
用于 product-os-server

MIT 许可证

28KB
361 代码行

Axum_CSRF

为基于axum的Web应用程序提供CSRF(跨站请求伪造)保护层的库。目前支持Axum 0.6版本。

crates.io docs.rs Minimum Rust Version

帮助

如果您需要关于此库的帮助,请加入我们的 Discord群组

安装

# Cargo.toml
[dependencies]
axum_csrf = "0.9.0"

Cargo功能标志

默认: []

layer:禁用状态并启用服务层。对于中间件交互很有用。

示例

通过共享状态将其添加到axum

use askama::Template;
use axum::{Form, response::IntoResponse, routing::get, Router};
use axum_csrf::{CsrfConfig, CsrfToken};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;

#[derive(Template, Deserialize, Serialize)]
#[template(path = "template.html")]
struct Keys {
    authenticity_token: String,
    // Your attributes...
}

#[tokio::main]
async fn main() {
    // initialize tracing
    tracing_subscriber::fmt::init();
    let config = CsrfConfig::default();

    // build our application with a route
    let app = Router::new()
        // `GET /` goes to `root` and Post Goes to check key
        .route("/", get(root).post(check_key))
        .with_state(config);

    // run our app with hyper
    // `axum::Server` is a re-export of `hyper::Server`
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    tracing::debug!("listening on {}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

// root creates the CSRF Token and sends it into the page for return.
async fn root(token: CsrfToken) -> impl IntoResponse {
    let keys = Keys {
        //this Token is a hashed Token. it is returned and the original token is hashed for comparison.
        authenticity_token: token.authenticity_token().unwrap(),
    };

    // We must return the token so that into_response will run and add it to our response cookies.
    (token, keys).into_response()
}

async fn check_key(token: CsrfToken, Form(payload): Form<Keys>) -> &'static str {
    // Verfiy the Hash and return the String message.
    if token.verify(&payload.authenticity_token).is_err() {
        "Token is invalid"
    } else {
        "Token is Valid lets do stuff!"
    }
}

模板文件

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Example</title>
    </head>

    <body>
        <form method="post" action="/">
            <input type="hidden" name="authenticity_token" value="{{ authenticity_token }}"/>
            <input id="button" type="submit" value="Submit" tabindex="4" />
        </form>
    </body>
</html>

如果您不想使用状态,请使用“layer”功能

use askama::Template;
use axum::{Form, response::IntoResponse, routing::get, Router};
use axum_csrf::{CsrfConfig, CsrfLayer, CsrfToken };
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;

#[derive(Template, Deserialize, Serialize)]
#[template(path = "template.html")]
struct Keys {
    authenticity_token: String,
    // Your attributes...
}

#[tokio::main]
async fn main() {
    // initialize tracing
    tracing_subscriber::fmt::init();
    let config = CsrfConfig::default();

    // build our application with a route
    let app = Router::new()
        // `GET /` goes to `root` and Post Goes to check key
        .route("/", get(root).post(check_key))
        .layer(CsrfLayer::new(config));

    // run our app with hyper
    // `axum::Server` is a re-export of `hyper::Server`
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    tracing::debug!("listening on {}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

// root creates the CSRF Token and sends it into the page for return.
async fn root(token: CsrfToken) -> impl IntoResponse {
    let keys = Keys {
        //this Token is a hashed Token. it is returned and the original token is hashed for comparison.
        authenticity_token: token.authenticity_token().unwrap(),
    };

    // We must return the token so that into_response will run and add it to our response cookies.
    (token, keys).into_response()
}

async fn check_key(token: CsrfToken, Form(payload): Form<Keys>) -> &'static str {
    // Verfiy the Hash and return the String message.
    if token.verify(&payload.authenticity_token).is_err() {
        "Token is invalid"
    } else {
        "Token is Valid lets do stuff!"
    }
}

如果您已经有了用于私有cookie的加密密钥,请以不同的方式构建CSRF配置

let cookie_key = cookie::Key::generate();
let config = CsrfConfig::default().with_key(Some(cookie_key));

let app = Router::new().with_state(config)

使用CSRF防止Post重放攻击。

如果您想防止Post重放攻击,则应使用会话存储方法。您将散列存储在服务器端会话存储中,并将其与表单一起发送。当它们提交数据时,您将首先检查表单的散列,然后将其与内部会话数据进行比较。当第二个散列有效时,您将然后从会话中删除散列。这防止了重放攻击并确保没有数据被篡改。如果您需要会话数据库,我建议使用 axum_session

使用 axum_session 的更改。

async fn greet(token: CsrfToken, session: Session<SessionPgPool>) -> impl IntoResponse {
    let authenticity_token = token.authenticity_token();
    session.set("authenticity_token", authenticity_token.clone()).await;

    let keys = Keys {
        authenticity_token,
    }

    //we must return the token so that into_response will run and add it to our response cookies.
    (token, keys).into_response()
}

验证CSRF密钥并验证Post重放攻击

async fn check_key(token: CsrfToken, session: Session<SessionPgPool>, Form(payload): Form<Keys>,) -> &'static str {
    let authenticity_token: String = session.get("authenticity_token").await.unwrap_or_default();

    if let Err(_) = token.verify(&payload.authenticity_token) {
        "Token is invalid"
    } else if let Err(_) = token.verify(&authenticity_token) {
        "Modification of both Cookie/token OR a replay attack occured"
    } else {
        // we remove it to only allow one post per generated token.
        session.remove("authenticity_token").await;
        "Token is Valid lets do stuff!"
    }
}

依赖项

~5MB
~94K SLoC