Rust 웹 앱에서 casbin 인증을 사용하는 방법 [파트 - 3]
47804 단어 casbinrustauthorization
다음은 참조용 github 저장소에 대한 링크입니다.
https://github.com/casbin-rs/examples/tree/master/actix-middleware-example
JWT를 지원하는 Actix-web, Casbin 및 Diesel을 사용하여 간단한 익명 포럼 앱을 만들 것입니다.
이 앱에는
admin
및 user
의 2가지 역할이 있습니다.시작하겠습니다.
먼저
Cargo.toml
-[dependencies]
http = "0.2.1"
actix = "0.11.0"
actix-web = "3.3.2"
actix-service = "2.0.0"
actix-rt = "1.1.1"
actix-cors = "0.4.0"
futures = "0.3.5"
failure = "0.1.8"
serde = "1.0.116"
serde_derive = "1.0.116"
serde_json = "1.0.57"
derive_more = "0.99.10"
chrono = { version = "0.4.18", features = ["serde"] }
diesel = { version = "1.4.5", features = ["postgres","r2d2", "chrono"] }
diesel_migrations = "1.4.0"
dotenv = "0.15.0"
env_logger = "0.8.1"
log = "0.4.11"
jsonwebtoken = "7.2.0"
bcrypt = "0.9.0"
csv = "1.1.3"
walkdir = "2.3.1"
actix-casbin= {version = "0.4.2", default-features = false, features = [ "runtime-async-std" ]}
actix-casbin-auth = {version = "0.4.4", default-features = false, features = [ "runtime-async-std" ]}
diesel-adapter = { version = "0.8.1", default-features = false, features = ["postgres","runtime-async-std"] }
uuid = {version = "0.8.1", features = ["v4"] }
casbin.conf
를 포함합니다. (리포지토리 참조)그리고
preset_policy.csv
. (리포지토리 참조)생성
.env
-APP_HOST=127.0.0.1
APP_PORT=8080
DATABASE_URL=postgres://databasename:[email protected]:5432/test
POOL_SIZE=8
HASH_ROUNDS=12
그런 다음 src 폴더에서
main.rs
-외부 상자를 먼저 가져오고 모듈(나중에 만들 예정) -
#![allow(proc_macro_derive_resolution_fallback)]
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate log;
#[macro_use]
extern crate diesel_migrations;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;
use crate::utils::csv_utils::{load_csv, walk_csv};
use actix::Supervisor;
use actix_casbin::casbin::{
function_map::key_match2, CachedEnforcer, CoreApi, DefaultModel, MgmtApi, Result,
};
use actix_casbin::CasbinActor;
use actix_casbin_auth::CasbinService;
use actix_cors::Cors;
use actix_web::middleware::normalize::TrailingSlash;
use actix_web::middleware::Logger;
use actix_web::middleware::NormalizePath;
use actix_web::{App, HttpServer};
use diesel_adapter::DieselAdapter;
use std::env;
mod api;
mod config;
mod constants;
mod errors;
mod middleware;
mod models;
mod routers;
mod schema;
mod services;
mod utils;
HttpServer
를 사용하여 서버를 생성합니다.APP_HOST
와 같은 모든 기본값은 .env
에 정의되어 있습니다.연결 풀을 정의합니다.
let pool = config::db::migrate_and_config_db(&database_url, pool_size);
기본 풀 크기는 8입니다.
우리는 캐빈 모델을 가져옵니다.
let model = DefaultModel::from_file("casbin.conf").await?;
let adapter = DieselAdapter::new(database_url, pool_size)?;
let mut casbin_middleware = CasbinService::new(model, adapter).await.unwrap();
casbin_middleware
.write()
.await
.get_role_manager()
.write()
.unwrap()
.matching_fn(Some(key_match2), None);
let share_enforcer = casbin_middleware.get_enforcer();
let clone_enforcer = share_enforcer.clone();
let casbin_actor = CasbinActor::<CachedEnforcer>::set_enforcer(share_enforcer)?;
let started_actor = Supervisor::start(|_| casbin_actor);
let preset_rules = load_csv(walk_csv("."));
for mut policy in preset_rules {
let ptype = policy.remove(0);
if ptype.starts_with('p') {
match clone_enforcer.write().await.add_policy(policy).await {
Ok(_) => info!("Preset policies(p) add successfully"),
Err(err) => error!("Preset policies(p) add error: {}", err.to_string()),
};
continue;
} else if ptype.starts_with('g') {
match clone_enforcer
.write()
.await
.add_named_grouping_policy(&ptype, policy)
.await
{
Ok(_) => info!("Preset policies(p) add successfully"),
Err(err) => error!("Preset policies(g) add error: {}", err.to_string()),
};
continue;
} else {
unreachable!()
}
}
그런 다음 모듈을 정의할 수 있습니다.
src/
디렉토리 안에 다음 디렉토리를 만듭니다.또한 다음 파일을 만드십시오 -
api/
및 config/
.middleware/
에서 모델 생성 -models/
, routers/
, services/
및 utils/
프로젝트의 루트에서 다음 명령을 실행하십시오.
cargo install diesel_cli --no-default-features --features postgres
.env Diesel이 Postgres 인스턴스의 연결 세부 정보를 가져오는 데 사용할 DATABASE_URL 속성입니다.
이제 프로젝트 루트 폴더에서
constants.rs
를 실행하십시오. Diesel은 새로운 데이터베이스(고백)와 일련의 빈 마이그레이션을 생성합니다.이제 실행 -
diesel migration generate casbin_rules post users ⏎
diesel migration run
이것은
errors.rs
디렉토리에 models/
를 생성합니다. 당신은 그것을 확인할 수 있습니다.이전에 생성한
post.rs
디렉토리에서 데이터베이스 구성을 정의합니다.pub fn migrate_and_config_db(url: &str, pool_size: u32) -> Pool {
info!("Migrating and configurating database...");
let manager = ConnectionManager::<Connection>::new(url);
let pool = r2d2::Pool::builder()
.connection_timeout(Duration::from_secs(10))
.max_size(pool_size)
.build(manager)
.expect("Failed to create pool.");
embedded_migrations::run(&pool.get().expect("Failed to migrate."))
.expect("Failed to migrate.");
pool
}
이제 미들웨어를 작성해 보자.
response.rs
디렉토리에서 파일 user.rs
을 만듭니다.여기에서 역할 기반 HTTP 인증을 구현합니다.
외부 크레이트 및 라이브러리 가져오기 -
#![allow(clippy::type_complexity)]
use crate::{
config::db::Pool, constants, models::response::ResponseBody, utils::token_utils,
};
use actix_casbin_auth::CasbinVals;
use actix_service::{Service, Transform};
use actix_web::{
dev::{ServiceRequest, ServiceResponse},
http::{HeaderName, HeaderValue, Method},
web::Data,
Error, HttpMessage, HttpResponse,
};
use futures::{
future::{ok, Ready},
Future,
};
use std::cell::RefCell;
use std::rc::Rc;
use std::{
pin::Pin,
task::{Context, Poll},
};
그런 다음 공개 구조체를 만듭니다.
pub struct Authentication;
이제 특성
user_token.rs
을 구현하십시오(문서 참조) -impl<S, B> Transform<S, ServiceRequest> for Authentication
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
B: MessageBody,
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = AuthenticationMiddleware<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ok(AuthenticationMiddleware {
service: Rc::new(RefCell::new(service)),
})
}
}
diesel setup
, schema.rs
, src/
, config/
및 middleware/
는 모두 특성 authn.rs
의 기본 구현에 정의된 관련 유형입니다.Transform
함수는 Response
를 반환합니다.우리는 또 다른 public 구조체를 만듭니다
Error
-pub struct AuthenticationMiddleware<S> {
service: Rc<RefCell<S>>,
}
InitError
에 대해 Transform
구현(문서 참조) -impl<S, B> Service<ServiceRequest> for AuthenticationMiddleware<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
B: MessageBody,
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
..
..
Future
는 Transform
특성에 대한 일반 new_transform
와 유사하게 Future
작동하도록 하는 기본 메서드입니다.AuthenticationMiddleware
impl 내부에 함수Service
를 정의하십시오.fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
..
..
}
AuthenticationMiddleware
함수 내에서 특정 변수를 정의합니다.스마트 포인터인 casbin
poll_ready
을 rust -Future
에 저장합니다.왜
poll
를 사용합니까? - 그 이유는 actixFuture
가 단일 스레드인 반면 우리의 casbincall
은 다중 스레드이므로 서비스가 각 스레드에 대한 포인터이기 때문입니다.그런 다음
Service
, call
및 service
-let mut srv = self.service.clone();
let mut authenticate_pass: bool = false;
let mut public_route: bool = false;
let mut authenticate_username: String = String::from("");
// Bypass some account routes
let headers = req.headers_mut();
headers.append(
HeaderName::from_static("content-length"),
HeaderValue::from_static("true"),
);
이것이 이 파일의 주요 논리입니다.
if Method::OPTIONS == *req.method() {
authenticate_pass = true;
} else {
for ignore_route in constants::IGNORE_ROUTES.iter() {
if req.path().starts_with(ignore_route) {
authenticate_pass = true;
public_route = true;
}
}
if !authenticate_pass {
if let Some(pool) = req.app_data::<Data<Pool>>() {
info!("Connecting to database...");
if let Some(authen_header) =
req.headers().get(constants::AUTHORIZATION)
{
info!("Parsing authorization header...");
if let Ok(authen_str) = authen_header.to_str() {
if authen_str.starts_with("bearer")
|| authen_str.starts_with("Bearer")
{
info!("Parsing token...");
let token = authen_str[6..].trim();
if let Ok(token_data) =
token_utils::decode_token(token.to_string())
{
info!("Decoding token...");
if token_utils::verify_token(&token_data, pool)
.is_ok()
{
info!("Valid token");
authenticate_username = token_data.claims.user;
authenticate_pass = true;
} else {
error!("Invalid token");
}
}
}
}
}
}
}
}
매우 간단합니다. db에 연결하고, 헤더에서 인증 토큰을 가져오고, 토큰을 구문 분석하고, 토큰을 디코딩하고, 토큰을 확인하고, 인증합니다.
그런 다음 casbin은 특정 사용자가 경로에 액세스할 수 있는 권한이 있는지 확인합니다.
if authenticate_pass {
if public_route {
let vals = CasbinVals {
subject: "anonymous".to_string(),
domain: None,
};
req.extensions_mut().insert(vals);
Box::pin(async move { srv.call(req).await })
} else {
let vals = CasbinVals {
subject: authenticate_username,
domain: None,
};
req.extensions_mut().insert(vals);
Box::pin(async move { srv.clone().call(req).await })
}
} else {
Box::pin(async move {
Ok(req.into_response(
HttpResponse::Unauthorized()
.json(ResponseBody::new(
constants::MESSAGE_INVALID_TOKEN,
constants::EMPTY,
))
.into_body(),
))
})
}
그런 다음 http 서버를 생성할 때
Rc<RefCell<S>>
에서 이것을 Rc<RefCell<>>
사용합니다. HttpServer::new(move || {
App::new()
.data(pool.clone())
.data(started_actor.clone())
.wrap(
Cors::new()
.send_wildcard()
.allowed_methods(vec!["GET", "POST", "DELETE"])
.allowed_headers(vec![
http::header::AUTHORIZATION,
http::header::ACCEPT,
])
.allowed_header(http::header::CONTENT_TYPE)
.max_age(3600)
.finish(),
)
.wrap(NormalizePath::new(TrailingSlash::Trim))
.wrap(Logger::default())
.wrap(casbin_middleware.clone())
.wrap(crate::middleware::authn::Authentication)
.configure(routers::routes)
})
.bind(&app_url)?
.run()
.await?;
그게 다야
이것이 actix-web 앱에서 casbin을 사용하는 방법입니다.
Reference
이 문제에 관하여(Rust 웹 앱에서 casbin 인증을 사용하는 방법 [파트 - 3]), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/smrpn/how-to-use-casbin-authorization-in-your-rust-web-app-part-3-4g2f텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)