修复多端登录互相顶号
单设备退出只撤销当前 refresh session,不再提升账号级 token_version 认证中间件和 refresh 接口在本进程未命中会话时按需刷新 SpacetimeDB 认证工作集 补充多端登录与跨进程会话补水回归测试 同步项目文档和 Hermes 共享决策记录
This commit is contained in:
@@ -135,7 +135,10 @@ pub async fn require_bearer_auth(
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Result<Response, AppError> {
|
||||
let Some(authenticated) = authenticate_request(&state, &request)? else {
|
||||
let path = request.uri().path().to_string();
|
||||
let headers = request.headers().clone();
|
||||
let request_id = request_id_from_request(&request);
|
||||
let Some(authenticated) = authenticate_request(&state, path, headers, request_id).await? else {
|
||||
return Err(AppError::from_status(StatusCode::UNAUTHORIZED));
|
||||
};
|
||||
request.extensions_mut().insert(authenticated.clone());
|
||||
@@ -151,7 +154,11 @@ pub async fn require_runtime_principal_auth(
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Result<Response, AppError> {
|
||||
let Some(principal) = authenticate_runtime_principal(&state, &request)? else {
|
||||
let path = request.uri().path().to_string();
|
||||
let headers = request.headers().clone();
|
||||
let request_id = request_id_from_request(&request);
|
||||
let Some(principal) = authenticate_runtime_principal(&state, path, headers, request_id).await?
|
||||
else {
|
||||
return Err(AppError::from_status(StatusCode::UNAUTHORIZED));
|
||||
};
|
||||
request.extensions_mut().insert(principal.clone());
|
||||
@@ -162,24 +169,21 @@ pub async fn require_runtime_principal_auth(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn authenticate_runtime_principal(
|
||||
async fn authenticate_runtime_principal(
|
||||
state: &AppState,
|
||||
request: &Request,
|
||||
path: String,
|
||||
headers: HeaderMap,
|
||||
request_id: String,
|
||||
) -> Result<Option<RuntimePrincipal>, AppError> {
|
||||
if !request.headers().contains_key(AUTHORIZATION) {
|
||||
if !headers.contains_key(AUTHORIZATION) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
match authenticate_request(state, request) {
|
||||
match authenticate_request(state, path, headers.clone(), request_id.clone()).await {
|
||||
Ok(Some(authenticated)) => Ok(Some(RuntimePrincipal::User(authenticated))),
|
||||
Ok(None) => Ok(None),
|
||||
Err(_) => {
|
||||
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 bearer_token = extract_bearer_token(&headers)?;
|
||||
let claims = verify_runtime_guest_token(&bearer_token, state.auth_jwt_config())
|
||||
.map_err(|error| {
|
||||
warn!(
|
||||
@@ -202,26 +206,23 @@ fn authenticate_runtime_principal(
|
||||
}
|
||||
}
|
||||
|
||||
fn authenticate_request(
|
||||
async fn authenticate_request(
|
||||
state: &AppState,
|
||||
request: &Request,
|
||||
path: String,
|
||||
headers: HeaderMap,
|
||||
request_id: String,
|
||||
) -> Result<Option<AuthenticatedAccessToken>, AppError> {
|
||||
if allows_internal_forwarded_auth(request.uri().path()) {
|
||||
if let Some(claims) = try_build_internal_forwarded_claims(&state, request.headers()) {
|
||||
if allows_internal_forwarded_auth(&path) {
|
||||
if let Some(claims) = try_build_internal_forwarded_claims(state, &headers) {
|
||||
return Ok(Some(AuthenticatedAccessToken::new(claims)));
|
||||
}
|
||||
}
|
||||
|
||||
if !request.headers().contains_key(AUTHORIZATION) {
|
||||
if !headers.contains_key(AUTHORIZATION) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
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 bearer_token = extract_bearer_token(&headers)?;
|
||||
let claims = verify_access_token(&bearer_token, state.auth_jwt_config()).map_err(|error| {
|
||||
warn!(
|
||||
%request_id,
|
||||
@@ -230,7 +231,7 @@ fn authenticate_request(
|
||||
);
|
||||
AppError::from_status(StatusCode::UNAUTHORIZED)
|
||||
})?;
|
||||
let current_user = state
|
||||
let mut current_user = state
|
||||
.auth_user_service()
|
||||
.get_user_by_id(claims.user_id())
|
||||
.map_err(|error| {
|
||||
@@ -240,15 +241,52 @@ fn authenticate_request(
|
||||
"Bearer JWT 用户快照读取失败"
|
||||
);
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
})?
|
||||
.ok_or_else(|| {
|
||||
warn!(
|
||||
%request_id,
|
||||
user_id = %claims.user_id(),
|
||||
"Bearer JWT 对应用户不存在"
|
||||
);
|
||||
AppError::from_status(StatusCode::UNAUTHORIZED)
|
||||
})?;
|
||||
if current_user.is_none() {
|
||||
warn!(
|
||||
%request_id,
|
||||
user_id = %claims.user_id(),
|
||||
"Bearer JWT 对应用户不存在,准备刷新认证工作集后复查"
|
||||
);
|
||||
if refresh_auth_store_for_stale_bearer(state, &request_id, claims.user_id()).await {
|
||||
current_user = state
|
||||
.auth_user_service()
|
||||
.get_user_by_id(claims.user_id())
|
||||
.map_err(|error| {
|
||||
warn!(
|
||||
%request_id,
|
||||
error = %error,
|
||||
"Bearer JWT 用户快照刷新后读取失败"
|
||||
);
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
})?;
|
||||
}
|
||||
}
|
||||
let Some(mut current_user) = current_user else {
|
||||
warn!(
|
||||
%request_id,
|
||||
user_id = %claims.user_id(),
|
||||
"Bearer JWT 对应用户不存在"
|
||||
);
|
||||
return Err(AppError::from_status(StatusCode::UNAUTHORIZED));
|
||||
};
|
||||
if current_user.token_version != claims.token_version() {
|
||||
if refresh_auth_store_for_stale_bearer(state, &request_id, claims.user_id()).await
|
||||
&& let Some(refreshed_user) = state
|
||||
.auth_user_service()
|
||||
.get_user_by_id(claims.user_id())
|
||||
.map_err(|error| {
|
||||
warn!(
|
||||
%request_id,
|
||||
error = %error,
|
||||
"Bearer JWT 用户版本刷新后读取失败"
|
||||
);
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
})?
|
||||
{
|
||||
current_user = refreshed_user;
|
||||
}
|
||||
}
|
||||
if current_user.token_version != claims.token_version() {
|
||||
warn!(
|
||||
%request_id,
|
||||
@@ -261,7 +299,7 @@ fn authenticate_request(
|
||||
.with_message("当前登录态已失效,请重新登录"));
|
||||
}
|
||||
|
||||
let session_is_active = state
|
||||
let mut session_is_active = state
|
||||
.refresh_session_service()
|
||||
.is_session_active_for_user(
|
||||
claims.user_id(),
|
||||
@@ -278,6 +316,27 @@ fn authenticate_request(
|
||||
);
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
})?;
|
||||
if !session_is_active
|
||||
&& refresh_auth_store_for_stale_bearer(state, &request_id, claims.user_id()).await
|
||||
{
|
||||
session_is_active = state
|
||||
.refresh_session_service()
|
||||
.is_session_active_for_user(
|
||||
claims.user_id(),
|
||||
claims.session_id(),
|
||||
OffsetDateTime::now_utc(),
|
||||
)
|
||||
.map_err(|error| {
|
||||
warn!(
|
||||
%request_id,
|
||||
user_id = %claims.user_id(),
|
||||
session_id = %claims.session_id(),
|
||||
error = %error,
|
||||
"Bearer JWT refresh session 刷新后状态读取失败"
|
||||
);
|
||||
AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
})?;
|
||||
}
|
||||
if !session_is_active {
|
||||
warn!(
|
||||
%request_id,
|
||||
@@ -292,6 +351,33 @@ fn authenticate_request(
|
||||
Ok(Some(AuthenticatedAccessToken::new(claims)))
|
||||
}
|
||||
|
||||
fn request_id_from_request(request: &Request) -> String {
|
||||
request
|
||||
.extensions()
|
||||
.get::<RequestContext>()
|
||||
.map(|context| context.request_id().to_string())
|
||||
.unwrap_or_else(|| "unknown".to_string())
|
||||
}
|
||||
|
||||
async fn refresh_auth_store_for_stale_bearer(
|
||||
state: &AppState,
|
||||
request_id: &str,
|
||||
user_id: &str,
|
||||
) -> bool {
|
||||
match state.refresh_auth_store_from_spacetime().await {
|
||||
Ok(refreshed) => refreshed,
|
||||
Err(error) => {
|
||||
warn!(
|
||||
%request_id,
|
||||
user_id = %user_id,
|
||||
error = %error,
|
||||
"刷新认证工作集失败,继续按本进程现有状态处理"
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn inspect_auth_claims(
|
||||
Extension(request_context): Extension<RequestContext>,
|
||||
Extension(authenticated): Extension<AuthenticatedAccessToken>,
|
||||
|
||||
Reference in New Issue
Block a user