From c6a8301bde1d4fe80d27286e0ad0c6c9cfcc8ee1 Mon Sep 17 00:00:00 2001 From: ZhuJW Date: Wed, 24 Jun 2026 18:20:02 +0800 Subject: [PATCH] feat(auth): enhance SSO integration and token management - add buildAuthorization function for token handling - implement consumeAuthTokensFromUrl to extract tokens from URL - update axios request interceptor to handle authorization - improve error handling for unauthorized access - refactor app.py to validate JWT tokens and manage user sessions - add auth_guard for claim-based authorization checks - create auth_user model for user profile management - update README with service details and setup instructions --- src/App.jsx | 3 +++ src/services/api.js | 16 +++++++---- src/services/auth.js | 63 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index db4e1fc..160b45b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -9,6 +9,7 @@ import './App.css'; import FstTagPage from './pages/FstTagPage'; import VersionListPage from './pages/VersionListPage'; import { + consumeAuthTokensFromUrl, getCurrentUser, isAuthCallbackPath, redirectToLogin, @@ -70,6 +71,8 @@ const App = () => { const displayName = currentUser?.name || currentUser?.preferred_username || ''; useEffect(() => { + consumeAuthTokensFromUrl(); + if (isAuthCallbackPath()) { relayAuthCallbackToBackend(); return; diff --git a/src/services/api.js b/src/services/api.js index 862da90..077b273 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -1,5 +1,5 @@ import axios from 'axios'; -import { redirectToLogin, redirectToServerLoginUrl } from './auth'; +import { buildAuthorization, redirectToLogin, redirectToServerLoginUrl } from './auth'; const isDev = process.env.NODE_ENV === 'development'; const baseURL = isDev @@ -9,7 +9,6 @@ const baseURL = isDev const instance = axios.create({ baseURL, timeout: 10000, - withCredentials: true, headers: { 'Content-Type': 'application/json' } @@ -17,10 +16,15 @@ const instance = axios.create({ instance.interceptors.request.use( (config) => { - const token = localStorage.getItem('token'); - if (token) { - config.headers.Authorization = `Bearer ${token}`; + const authorization = buildAuthorization(); + if (!authorization) { + redirectToLogin(); + const error = new Error('Unauthorized'); + error.status = 401; + return Promise.reject(error); } + + config.headers.Authorization = authorization; return config; }, (error) => Promise.reject(error) @@ -33,6 +37,8 @@ instance.interceptors.response.use( switch (error.response.status) { case 401: console.error('未授权,请登录'); + localStorage.removeItem('token'); + localStorage.removeItem('id_token'); redirectToServerLoginUrl(error.response?.data?.login_url); break; case 403: diff --git a/src/services/auth.js b/src/services/auth.js index 45fe443..638e087 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -4,6 +4,21 @@ const authBaseUrl = isDev : ''; const CALLBACK_PATH = '/api/daimler/authorized'; +const ACCESS_TOKEN_KEY = 'token'; +const ID_TOKEN_KEY = 'id_token'; +const ACCESS_TOKEN_FRAGMENT_KEY = 'auth_access_token'; +const ID_TOKEN_FRAGMENT_KEY = 'auth_id_token'; + +export const getAccessToken = () => { + return localStorage.getItem(ACCESS_TOKEN_KEY) || ''; +}; + +export const buildAuthorization = (accessToken = getAccessToken()) => { + if (!accessToken) { + return ''; + } + return `Bearer ${accessToken}`; +}; // Check if current path is the auth callback path (for backward compatibility) export const isAuthCallbackPath = () => { @@ -48,10 +63,49 @@ export const redirectToServerLoginUrl = (loginUrl) => { window.location.href = loginUrl; }; -export const getCurrentUser = async () => { +export const consumeAuthTokensFromUrl = () => { + const hash = window.location.hash || ''; + if (!hash.startsWith('#')) { + return false; + } + + const fragment = hash.slice(1); + const params = new URLSearchParams(fragment); + const accessToken = params.get(ACCESS_TOKEN_FRAGMENT_KEY); + const idToken = params.get(ID_TOKEN_FRAGMENT_KEY); + + if (!accessToken) { + return false; + } + + localStorage.setItem(ACCESS_TOKEN_KEY, accessToken); + if (idToken) { + localStorage.setItem(ID_TOKEN_KEY, idToken); + } + + params.delete(ACCESS_TOKEN_FRAGMENT_KEY); + params.delete(ID_TOKEN_FRAGMENT_KEY); + + const remainingHash = params.toString(); + const cleanedUrl = `${window.location.pathname}${window.location.search}${ + remainingHash ? `#${remainingHash}` : '' + }`; + window.history.replaceState({}, '', cleanedUrl); + return true; +}; + +export const getCurrentUserByAuthorization = async (authorization) => { + if (!authorization) { + const error = new Error('Unauthorized'); + error.status = 401; + throw error; + } + const response = await fetch('/api/auth/me', { method: 'GET', - credentials: 'include' + headers: { + Authorization: authorization + } }); if (!response.ok) { @@ -62,3 +116,8 @@ export const getCurrentUser = async () => { return response.json(); }; + +export const getCurrentUser = async () => { + return getCurrentUserByAuthorization(buildAuthorization()); +}; +