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/// Helper function to get client IP from request
17#[cfg(feature = "audit")]
18fn get_client_ip(req: &ServiceRequest) -> String {
19    req.connection_info()
20        .realip_remote_addr()
21        .unwrap_or("-")
22        .to_string()
23}
24
25/// Security middleware factory.
26///
27/// # Spring Equivalent
28/// `SecurityFilterChain`
29///
30/// # Example
31/// ```ignore
32/// App::new().wrap(
33///     SecurityTransform::new()
34///         .config_authenticator(my_authenticator)
35///         .config_authorizer(my_authorizer)
36/// )
37/// ```
38pub struct SecurityTransform<Auth, Autho> {
39    authenticator: Option<fn() -> Auth>,
40    authorizer: Option<fn() -> Autho>,
41}
42
43impl<Auth, Autho> SecurityTransform<Auth, Autho> {
44    pub fn new() -> Self {
45        SecurityTransform {
46            authorizer: None,
47            authenticator: None,
48        }
49    }
50
51    pub fn config_authenticator(mut self, authenticator: fn() -> Auth) -> Self {
52        self.authenticator = Some(authenticator);
53        self
54    }
55
56    pub fn config_authorizer(mut self, authorizer: fn() -> Autho) -> Self {
57        self.authorizer = Some(authorizer);
58        self
59    }
60}
61
62impl<Auth, Autho> Default for SecurityTransform<Auth, Autho> {
63    fn default() -> Self {
64        Self::new()
65    }
66}
67
68impl<S, B, Auth, Autho> Transform<S, ServiceRequest> for SecurityTransform<Auth, Autho>
69where
70    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
71    S::Future: 'static,
72    B: 'static,
73    Auth: Authenticator + 'static,
74    Autho: Authorizer<B> + 'static,
75{
76    type Response = ServiceResponse<EitherBody<B>>;
77    type Error = Error;
78    type Transform = SecurityService<Auth, Autho, S>;
79    type InitError = ();
80    type Future = Ready<Result<Self::Transform, Self::InitError>>;
81
82    fn new_transform(&self, service: S) -> Self::Future {
83        let authenticator = self.authenticator.map(|f| f());
84        let authorizer = self.authorizer.map(|f| f());
85
86        ok(SecurityService {
87            authenticator,
88            authorizer,
89            service: Rc::new(service),
90        })
91    }
92}
93
94/// Security middleware service.
95///
96/// # Spring Equivalent
97/// `FilterChainProxy`
98pub struct SecurityService<Auth, Autho, S> {
99    authenticator: Option<Auth>,
100    authorizer: Option<Autho>,
101    service: Rc<S>,
102}
103
104impl<Auth, Autho, S, B> Service<ServiceRequest> for SecurityService<Auth, Autho, S>
105where
106    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
107    S::Future: 'static,
108    B: 'static,
109    Auth: Authenticator,
110    Autho: Authorizer<B>,
111{
112    type Response = ServiceResponse<EitherBody<B>>;
113    type Error = Error;
114    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
115
116    actix_web::dev::forward_ready!(service);
117
118    fn call(&self, req: ServiceRequest) -> Self::Future {
119        let service = Rc::clone(&self.service);
120
121        // Step 1: Authenticate - extract user from request
122        let user = self
123            .authenticator
124            .as_ref()
125            .and_then(|auth| auth.get_user(&req));
126
127        // Audit log authentication result
128        #[cfg(feature = "audit")]
129        {
130            let path = req.path().to_string();
131            let method = req.method().to_string();
132            let ip = get_client_ip(&req);
133
134            if let Some(ref u) = user {
135                tracing::info!(
136                    target: "actix_security::audit",
137                    event_type = "AUTHENTICATION_SUCCESS",
138                    user = %u.get_username(),
139                    ip = %ip,
140                    path = %path,
141                    method = %method,
142                    "User authenticated successfully"
143                );
144            } else if self.authenticator.is_some() {
145                tracing::debug!(
146                    target: "actix_security::audit",
147                    event_type = "AUTHENTICATION_ANONYMOUS",
148                    ip = %ip,
149                    path = %path,
150                    method = %method,
151                    "Anonymous request (no credentials provided)"
152                );
153            }
154        }
155
156        // Step 2: Store user in request extensions (if authenticated)
157        // This makes the user available to handlers via AuthenticatedUser extractor
158        if let Some(ref u) = user {
159            req.extensions_mut().insert(u.clone());
160        }
161
162        // Step 3: Process authorization
163        if let Some(authorizer) = &self.authorizer {
164            // Create a closure to call the next service
165            let next = move |req: ServiceRequest| -> LocalBoxFuture<'static, Result<ServiceResponse<B>, Error>> {
166                let fut = service.call(req);
167                Box::pin(fut)
168            };
169
170            authorizer.process(req, user.as_ref(), next)
171        } else {
172            // No authorizer configured, pass through with EitherBody::left
173            let fut = service.call(req);
174            Box::pin(async move {
175                let res = fut.await?;
176                Ok(res.map_into_left_body())
177            })
178        }
179    }
180}