4 个稳定版本

2.0.1 2023年2月15日
1.0.1 2022年8月16日
0.1.0 2022年8月16日

#484身份验证

30 每月下载

Apache-2.0

20KB
108

axum-casbin-auth

此库实现了 Tower 的 Service、Layer 特性,用于授权服务,使用 Casbin。请阅读 Casbin 了解所有支持的模型和功能。基本思路是有一组 Casbin 策略,服务期望在请求中有一个包含 Subject: String 的结构体。然后,服务会与 casbin 定义的策略进行匹配,并尝试授权主体。如果失败,层会以 UNAUTHORISED 响应拒绝。您可以在 这里 找到许多支持的授权模型。以下是用于 RESTful 模型的库的基本用法。

安装

使用 npm 安装 my-project

Cargo.toml

[dependencies]
# Other deps
axum-casbin-auth = "*"

代码使用

jwt.rs

use std::str::FromStr;

use crate::constants::JWT_SECRET;
use async_trait::async_trait;
use axum::extract::{FromRequest, TypedHeader};
use axum::RequestPartsExt;
use axum::http::request::Parts;
use axum::http::Extensions;
use axum::headers::{authorization::Bearer, Authorization};
use axum::response::IntoResponse;
use axum::{http::StatusCode, Json};
use axum_casbin_auth::CasbinAuthClaims;
use chrono::{Duration, Utc};
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use serde_json::json;

type Result<T> = std::result::Result<T, Error>;

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Claims {
    pub subject: String,
    pub exp: i64,
    pub iat: i64,
    pub user_id: i32,
}

impl Claims {
    pub fn new(email: String, user_id: i32, role: String) -> Self {
        let iat = Utc::now();
        let exp = iat + Duration::hours(24);
        Self {
            subject: email,
            iat: iat.timestamp(),
            exp: exp.timestamp(),
            user_id,
            role: ROLES::from_str(&role).unwrap(),
        }
    }
}

pub fn sign(email: String, user_id: i32, role: String) -> ErrorResult<String> {
    Ok(jsonwebtoken::encode(
        &Header::default(),
        &Claims::new(email, user_id, role),
        &EncodingKey::from_secret(JWT_SECRET.as_bytes()),
    )?)
}

pub fn verify(token: &str) -> ErrorResult<Claims> {
    Ok(jsonwebtoken::decode(
        token,
        &DecodingKey::from_secret(JWT_SECRET.as_bytes()),
        &Validation::default(),
    )
    .map(|data| data.claims)?)
}

#[derive(Debug)]
pub enum AuthError {
    WrongCredentials,
    MissingCredentials,
    TokenCreation,
    InvalidToken,
}

#[axum::async_trait]
impl<B> FromRequestParts<B> for Claims
where
    B: Send + Sync,
{
    type Rejection = AuthError;

    async fn from_request_parts(parts: &mut Parts, _state: &B) -> Result<Self, Self::Rejection> {
        let TypedHeader(Authorization(bearer)) = parts
            .extract::<TypedHeader<Authorization<Bearer>>>()
            .await
            .map_err(|_| AuthError::InvalidToken)?;

        let token_data = verify(bearer.token()).map_err(|e| {
            println!("{:?}", e);
            AuthError::InvalidToken
        })?;
        let mut claims_extension = Extensions::new();
        claims_extension.insert(CasbinAuthClaims::new(token_data.clone().subject));
        let _ = parts.extensions.extend(claims_extension);
        Ok(token_data)
    }
}

impl IntoResponse for AuthError {
    fn into_response(self) -> axum::response::Response {
        let (status, error_message) = match self {
            AuthError::WrongCredentials => (StatusCode::UNAUTHORIZED, "Wrong credentials"),
            AuthError::MissingCredentials => (StatusCode::BAD_REQUEST, "Missing credentials"),
            AuthError::TokenCreation => (StatusCode::INTERNAL_SERVER_ERROR, "Token creation error"),
            AuthError::InvalidToken => (StatusCode::BAD_REQUEST, "Invalid token"),
        };
        let body = Json(json!({
            "error": error_message,
        }));
        (status, body).into_response()
    }
}

在项目根目录下创建一个名为 casbin(或任何名称)的文件夹,并创建两个文件。

  1. model.conf
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)
  1. policy.csv
p, admin@test.com, *, POST

请阅读 Casbin 文档以获取详细说明

main.rs

use axum_casbin_auth::{
    casbin::{CoreApi, Enforcer},
    CasbinAuthLayer,
};

use lib::utils::jwt::Claims;
// ... server code

let e = Enforcer::new("casbin/model.conf", "casbin/policy.csv").await?;
let casbin_auth_enforcer = Arc::new(RwLock::new(e));

let app = Router::new()
    .route("/inventory", post(add_product::handle))
    // Order is important
    .layer(CasbinAuthLayer::new(casbin_auth_enforcer))
    .layer(from_extractor::<Claims>());

// ...

在此之后,任何请求都将由 Claims 提取器进行身份验证,并将包含主体的 CasbinAuthClaims 注入到请求中,然后将其传递给 CasbinAuthLayer,请求将根据策略进行授权。策略也可以在数据库中动态存储和维护。您可以在 这里 找到更多适配器。

依赖关系

~22–33MB
~591K SLoC