4 个稳定版本
2.0.1 | 2023年2月15日 |
---|---|
1.0.1 | 2022年8月16日 |
0.1.0 |
|
#484 在 身份验证
30 每月下载
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(或任何名称)的文件夹,并创建两个文件。
- 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)
- 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