actix / actix-extras

A collection of additional crates supporting the actix and actix-web frameworks.
https://actix.rs
Apache License 2.0
760 stars 191 forks source link

How to implement login status check through a middleware in actix_identity #445

Closed le0lu0 closed 1 month ago

le0lu0 commented 2 months ago

https://docs.rs/actix-identity/latest/actix_identity/

I implemented the login function and the function of checking user information in the handler according to the instructions in the document, but it is obviously unreasonable to check the user login status in each handler, so I want to add a middleware to check whether the user is logged in. I added the AuthMiddleware and completed the registration, but in the AuthMiddleware, I can't get the identify

    // main.rs
    let private_key = actix_web::cookie::Key::generate();
    let redis_url = app_config.app.datasource.redis_url;
    let redis_store = RedisSessionStore::new(redis_url)
        .await
        .expect("Failed to create redis conn.");

    // create http server
    HttpServer::new(move || {
        let identity_middleware = IdentityMiddleware::default();
        // let identity_middleware = IdentityMiddleware::builder()
            // .logout_behaviour(LogoutBehaviour::PurgeSession)
            // .login_deadline(login_deadline_seconds)
            // .visit_deadline(visit_deadline_seconds)
            // .id_key(id_key)
            // .login_unix_timestamp_key(login_unix_timestamp_key)
            // .last_visit_unix_timestamp_key(last_visit_unix_timestamp_key)
            // .build();

        let session_middleware = SessionMiddleware::new(redis_store.clone(), private_key.clone());
        // let session_middleware = SessionMiddleware::builder(redis_store.clone(), private_key.clone())
            // .cookie_http_only(true)
            // .cookie_name("X-SESSION".into())
            // .cookie_path("/api".into())
            // .cookie_same_site(SameSite::Strict)
            // .cookie_content_security(CookieContentSecurity::Private)
            // .build();

        let auth_middleware = AuthMiddleware{ whitelist: whitelist.clone(), };

        let default_header_middleware = middleware::DefaultHeaders::new().add(("X-Version", "1.0.0"));

        let logger_middleware = Logger::default();

        let service = web::scope("/api")
            .configure(sys_api::routes::init);

        App::new()
            .app_data(json_config.clone())
            .app_data(web::Data::new(Arc::clone(&context)))
            .wrap(auth_middleware)
            .wrap(identity_middleware)
            .wrap(session_middleware)
            .wrap(default_header_middleware)
            .wrap(logger_middleware)
            .service(service)
    })
        .apply_settings(&app_config.actix)
        .run()
        .await
//auth_middleware.rs

use std::future::{ready, Ready};
use actix_web::middleware::Identity;
use actix_web::{dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, Error, FromRequest, HttpMessage, HttpResponse};
use actix_web::body::EitherBody;
use futures_util::future::LocalBoxFuture;

use app_base::config::app_config::WhitelistItem;

pub struct AuthMiddleware{
    pub whitelist: Vec<WhitelistItem>,
}
impl<S, B> Transform<S, ServiceRequest> for AuthMiddleware
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<EitherBody<B>>;
    type Error = Error;
    type InitError = ();
    type Transform = InnerAuthMiddleware<S>;
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ready(Ok(InnerAuthMiddleware { service, whitelist: self.whitelist.clone() }))
    }
}

pub struct InnerAuthMiddleware<S> {
    service: S,
    whitelist: Vec<WhitelistItem>
}

impl<S, B> Service<ServiceRequest> for InnerAuthMiddleware<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<EitherBody<B>>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    forward_ready!(service);

    fn call(&self, request: ServiceRequest) -> Self::Future {
        // check whitelist
        let whitelist = &self.whitelist;
        let request_path = request.path().to_string();
        let request_method = request.method().to_string();

        let is_whitelisted = whitelist.iter().any(|item| {
            item.path == request_path && item.method.eq_ignore_ascii_case(&request_method)
        });
        if is_whitelisted {
            let res = self.service.call(request);
            Box::pin(async move {
                // forwarded responses map to "left" body
                res.await.map(ServiceResponse::map_into_left_body)
            })
        }else{
            let identity = request.extensions().get::<Identity>().cloned();
            println!("identity: {:?}", identity);

            if let Some(identity) = identity {
                let res = self.service.call(request);
                Box::pin(async move {
                    // forwarded responses map to "left" body
                    res.await.map(ServiceResponse::map_into_left_body)
                })
            } else {
                let (request, _pl) = request.into_parts();
                let response = HttpResponse::Unauthorized()
                    .finish()
                    .map_into_right_body();
                return Box::pin(async { Ok(ServiceResponse::new(request, response)) });
            }
        }
    }
}

Print log after calling the interface >> identity: None