Skip to main content

actix_security_core/http/security/
middleware.rs

1//! Security middleware for Actix Web.
2//!
3//! # Spring Equivalent
4//! `SecurityFilterChain` / `FilterChainProxy`
5
6use std::rc::Rc;
7
8use actix_service::{Service, Transform};
9use actix_web::body::EitherBody;
10use actix_web::dev::{ServiceRequest, ServiceResponse};
11use actix_web::{Error, HttpMessage};
12use futures_util::future::{ok, LocalBoxFuture, Ready};
13
14use crate::http::security::config::{Authenticator, Authorizer};
15
16/// Security middleware factory.
17///
18/// # Spring Equivalent
19/// `SecurityFilterChain`
20///
21/// # Example
22/// ```ignore
23/// App::new().wrap(
24///     SecurityTransform::new()
25///         .config_authenticator(my_authenticator)
26///         .config_authorizer(my_authorizer)
27/// )
28/// ```
29pub struct SecurityTransform<Auth, Autho> {
30    authenticator: Option<fn() -> Auth>,
31    authorizer: Option<fn() -> Autho>,
32}
33
34impl<Auth, Autho> SecurityTransform<Auth, Autho> {
35    pub fn new() -> Self {
36        SecurityTransform {
37            authorizer: None,
38            authenticator: None,
39        }
40    }
41
42    pub fn config_authenticator(mut self, authenticator: fn() -> Auth) -> Self {
43        self.authenticator = Some(authenticator);
44        self
45    }
46
47    pub fn config_authorizer(mut self, authorizer: fn() -> Autho) -> Self {
48        self.authorizer = Some(authorizer);
49        self
50    }
51}
52
53impl<Auth, Autho> Default for SecurityTransform<Auth, Autho> {
54    fn default() -> Self {
55        Self::new()
56    }
57}
58
59impl<S, B, Auth, Autho> Transform<S, ServiceRequest> for SecurityTransform<Auth, Autho>
60where
61    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
62    S::Future: 'static,
63    B: 'static,
64    Auth: Authenticator + 'static,
65    Autho: Authorizer<B> + 'static,
66{
67    type Response = ServiceResponse<EitherBody<B>>;
68    type Error = Error;
69    type Transform = SecurityService<Auth, Autho, S>;
70    type InitError = ();
71    type Future = Ready<Result<Self::Transform, Self::InitError>>;
72
73    fn new_transform(&self, service: S) -> Self::Future {
74        let authenticator = self.authenticator.map(|f| f());
75        let authorizer = self.authorizer.map(|f| f());
76
77        ok(SecurityService {
78            authenticator,
79            authorizer,
80            service: Rc::new(service),
81        })
82    }
83}
84
85/// Security middleware service.
86///
87/// # Spring Equivalent
88/// `FilterChainProxy`
89pub struct SecurityService<Auth, Autho, S> {
90    authenticator: Option<Auth>,
91    authorizer: Option<Autho>,
92    service: Rc<S>,
93}
94
95impl<Auth, Autho, S, B> Service<ServiceRequest> for SecurityService<Auth, Autho, S>
96where
97    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
98    S::Future: 'static,
99    B: 'static,
100    Auth: Authenticator,
101    Autho: Authorizer<B>,
102{
103    type Response = ServiceResponse<EitherBody<B>>;
104    type Error = Error;
105    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
106
107    actix_web::dev::forward_ready!(service);
108
109    fn call(&self, req: ServiceRequest) -> Self::Future {
110        let service = Rc::clone(&self.service);
111
112        // Step 1: Authenticate - extract user from request
113        let user = self
114            .authenticator
115            .as_ref()
116            .and_then(|auth| auth.get_user(&req));
117
118        // Step 2: Store user in request extensions (if authenticated)
119        // This makes the user available to handlers via AuthenticatedUser extractor
120        if let Some(ref u) = user {
121            req.extensions_mut().insert(u.clone());
122        }
123
124        // Step 3: Process authorization
125        if let Some(authorizer) = &self.authorizer {
126            // Create a closure to call the next service
127            let next = move |req: ServiceRequest| -> LocalBoxFuture<'static, Result<ServiceResponse<B>, Error>> {
128                let fut = service.call(req);
129                Box::pin(fut)
130            };
131
132            authorizer.process(req, user.as_ref(), next)
133        } else {
134            // No authorizer configured, pass through with EitherBody::left
135            let fut = service.call(req);
136            Box::pin(async move {
137                let res = fut.await?;
138                Ok(res.map_into_left_body())
139            })
140        }
141    }
142}