#axum #permissions #macro #attributes #access #resources #uri

axum_grants

一套用于保护Axum URI资源的属性宏

2个版本

0.1.3 2024年3月31日
0.1.2 2024年3月30日
0.1.1 2024年3月29日
0.1.0 2024年3月29日

#248 in 过程宏

Download history 6/week @ 2024-04-10

每月 151 次下载

MIT 许可证

12KB
163 代码行,不包括注释

Axum Grants

Axum Grants提供基于权限保护资源的两个Rust属性宏。资源权限作为参数传递给属性宏,当前用户的权限包含在Extension中。可以通过比较当前用户的权限是否包含资源权限来确定对资源的访问。Axum Grants的基本原理是通过宏将逻辑代码插入处理函数中,从而通过这段逻辑代码保护资源。
具体用法请参考axum_example crate。

准备工作

您需要定义一个结构体,它必须有一个perms字段;类型可以是Vec或HashSet。
当有大量权限项时,请使用HashSet,因为它的contains函数的时间复杂度为O(1)。
例如

#[derive(Debug, Clone, Default)]
pub struct Claims {
    pub user_id: u64, 
    pub user_name: String,
    ...
    pub perms: HashSet<String>, // must have this field
}

在您的token中间件中,将Claims插入Axum Extension;这里的Claims可以来自数据库、Redis或JWT。

async fn auth_middle_war(mut req: Request<Body>, next: Next) -> Response<Body> {
    // parse the claims from the token string
    let claims = verify_token(&APP_CFG.app.jwt_secret, token.as_str());
    
    // insert claims into extensions
    req.extensions_mut().insert(claims);
    next.run(req).await
}

protect属性宏的使用说明

当在您的Axum Handler上使用protect属性宏时
如果受保护的资源只需要单个权限来访问,您可以将权限设置为如下

#[protect("opt_qry")]

如果受保护的资源需要多个权限中的任何一个来访问,您可以将权限设置为如下

#[protect(any("opt_crt", "opt_upt", "opt_del"))]

如果受保护的资源需要多个权限的全部来访问,您可以将权限设置为如下

#[protect(all("opt_upt", "opt_del"))]

以下是一个通用示例

#[protect("opt_crt")]
async fn crt_handler(Extension(claims): Extension<Claims>) -> impl IntoResponse {
    // your business code
    ...
}

在上面的示例中,在protect属性宏展开后,最终的代码将如下所示

async fn crt_handler(Extension(claims): Extension<Claims>) -> impl IntoResponse {
    if !claims.perms.contains("opt_crt".to_string()) {
        return axum::http::Response::builder()
            .status(axum::http::StatusCode::FORBIDDEN)
            .body(Body::from("Insufficient permissions, need the permission: opt_crt "))
            .unwrap()
    }
    // your business code
    ...
}

protect_diy属性宏的使用说明

protect_diy宏设置权限的方法与protect宏的使用方法一致,但protect_diy宏允许您定义自己的Axum IntoResponse。
您需要提供一个没有字段的特定名称AxumGrantsResponse的结构体。这个结构体必须有一个具有签名的函数fn get_into_response(msg: &str) -> impl IntoResponse。
以下是一个通用示例

pub struct AxumGrantsResponse;
impl AxumGrantsResponse {
    pub fn get_into_response(msg: &str) -> impl IntoResponse {
        // axum::http::Response::builder()
        //     .status(axum::http::StatusCode::FORBIDDEN)
        //     .body(Body::from(msg.to_string())).unwrap()
        axum::Json(json!(
        {
            "cd": "403",
            "msg": msg,
        }))
    }
}

然后您可以使用它如下

#[protect_diy("opt_crt")]
async fn crt_handler(Extension(claims): Extension<Claims>) -> impl IntoResponse {
  // your business code
  ...
}

在上面的示例中,在protect_diy属性宏展开后,最终的代码将如下所示

async fn crt_handler(Extension(claims): Extension<Claims>) -> impl IntoResponse {
    if !claims.perms.contains("opt_crt".to_string()) {
        return return AxumGrantsResponse::get_into_response("Insufficient permissions, need the permission: opt_crt ").into_response();
    }
    // your business code
    ...
}

依赖项

~265–710KB
~17K SLoC