feat: add platform auth jwt adapter

This commit is contained in:
2026-04-21 13:02:44 +08:00
parent e37163d4d3
commit adaf514a1a
20 changed files with 1220 additions and 44 deletions

View File

@@ -0,0 +1,119 @@
use axum::{
Json,
extract::{Extension, Request, State},
http::{HeaderMap, StatusCode, header::AUTHORIZATION},
middleware::Next,
response::Response,
};
use platform_auth::{AccessTokenClaims, verify_access_token};
use serde_json::{Value, json};
use tracing::warn;
use crate::{
api_response::json_success_body, http_error::AppError, request_context::RequestContext,
state::AppState,
};
// 统一把已校验的 claims 写入 request extensions避免后续 handler 再次重复解析 Bearer token。
#[derive(Clone, Debug)]
pub struct AuthenticatedAccessToken {
claims: AccessTokenClaims,
}
impl AuthenticatedAccessToken {
pub fn new(claims: AccessTokenClaims) -> Self {
Self { claims }
}
pub fn claims(&self) -> &AccessTokenClaims {
&self.claims
}
}
pub async fn require_bearer_auth(
State(state): State<AppState>,
mut request: Request,
next: Next,
) -> Result<Response, AppError> {
let bearer_token = extract_bearer_token(request.headers())?;
let request_id = request
.extensions()
.get::<RequestContext>()
.map(|context| context.request_id().to_string())
.unwrap_or_else(|| "unknown".to_string());
let claims = verify_access_token(&bearer_token, state.auth_jwt_config()).map_err(|error| {
warn!(
%request_id,
error = %error,
"Bearer JWT 校验失败"
);
AppError::from_status(StatusCode::UNAUTHORIZED)
})?;
request
.extensions_mut()
.insert(AuthenticatedAccessToken::new(claims));
Ok(next.run(request).await)
}
pub async fn inspect_auth_claims(
Extension(request_context): Extension<RequestContext>,
Extension(authenticated): Extension<AuthenticatedAccessToken>,
) -> Json<Value> {
json_success_body(
Some(&request_context),
json!({
"claims": authenticated.claims(),
}),
)
}
fn extract_bearer_token(headers: &HeaderMap) -> Result<String, AppError> {
let authorization = headers
.get(AUTHORIZATION)
.and_then(|value| value.to_str().ok())
.map(str::trim)
.ok_or_else(|| AppError::from_status(StatusCode::UNAUTHORIZED))?;
let token = authorization
.strip_prefix("Bearer ")
.or_else(|| authorization.strip_prefix("bearer "))
.map(str::trim)
.filter(|token| !token.is_empty())
.ok_or_else(|| AppError::from_status(StatusCode::UNAUTHORIZED))?;
Ok(token.to_string())
}
#[cfg(test)]
mod tests {
use super::extract_bearer_token;
use axum::{
http::{HeaderMap, HeaderValue, StatusCode, header::AUTHORIZATION},
response::IntoResponse,
};
#[test]
fn extract_bearer_token_accepts_standard_header() {
let mut headers = HeaderMap::new();
headers.insert(
AUTHORIZATION,
HeaderValue::from_static("Bearer token-value"),
);
let token = extract_bearer_token(&headers).expect("bearer token should be extracted");
assert_eq!(token, "token-value");
}
#[test]
fn extract_bearer_token_rejects_missing_scheme() {
let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, HeaderValue::from_static("Basic abc"));
let error = extract_bearer_token(&headers).expect_err("basic auth should be rejected");
assert_eq!(error.into_response().status(), StatusCode::UNAUTHORIZED);
}
}