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
This commit is contained in:
ZhuJW
2026-06-24 18:20:02 +08:00
parent e999e8a886
commit c6a8301bde
3 changed files with 75 additions and 7 deletions

View File

@@ -9,6 +9,7 @@ import './App.css';
import FstTagPage from './pages/FstTagPage'; import FstTagPage from './pages/FstTagPage';
import VersionListPage from './pages/VersionListPage'; import VersionListPage from './pages/VersionListPage';
import { import {
consumeAuthTokensFromUrl,
getCurrentUser, getCurrentUser,
isAuthCallbackPath, isAuthCallbackPath,
redirectToLogin, redirectToLogin,
@@ -70,6 +71,8 @@ const App = () => {
const displayName = currentUser?.name || currentUser?.preferred_username || ''; const displayName = currentUser?.name || currentUser?.preferred_username || '';
useEffect(() => { useEffect(() => {
consumeAuthTokensFromUrl();
if (isAuthCallbackPath()) { if (isAuthCallbackPath()) {
relayAuthCallbackToBackend(); relayAuthCallbackToBackend();
return; return;

View File

@@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { redirectToLogin, redirectToServerLoginUrl } from './auth'; import { buildAuthorization, redirectToLogin, redirectToServerLoginUrl } from './auth';
const isDev = process.env.NODE_ENV === 'development'; const isDev = process.env.NODE_ENV === 'development';
const baseURL = isDev const baseURL = isDev
@@ -9,7 +9,6 @@ const baseURL = isDev
const instance = axios.create({ const instance = axios.create({
baseURL, baseURL,
timeout: 10000, timeout: 10000,
withCredentials: true,
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
@@ -17,10 +16,15 @@ const instance = axios.create({
instance.interceptors.request.use( instance.interceptors.request.use(
(config) => { (config) => {
const token = localStorage.getItem('token'); const authorization = buildAuthorization();
if (token) { if (!authorization) {
config.headers.Authorization = `Bearer ${token}`; redirectToLogin();
const error = new Error('Unauthorized');
error.status = 401;
return Promise.reject(error);
} }
config.headers.Authorization = authorization;
return config; return config;
}, },
(error) => Promise.reject(error) (error) => Promise.reject(error)
@@ -33,6 +37,8 @@ instance.interceptors.response.use(
switch (error.response.status) { switch (error.response.status) {
case 401: case 401:
console.error('未授权,请登录'); console.error('未授权,请登录');
localStorage.removeItem('token');
localStorage.removeItem('id_token');
redirectToServerLoginUrl(error.response?.data?.login_url); redirectToServerLoginUrl(error.response?.data?.login_url);
break; break;
case 403: case 403:

View File

@@ -4,6 +4,21 @@ const authBaseUrl = isDev
: ''; : '';
const CALLBACK_PATH = '/api/daimler/authorized'; 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) // Check if current path is the auth callback path (for backward compatibility)
export const isAuthCallbackPath = () => { export const isAuthCallbackPath = () => {
@@ -48,10 +63,49 @@ export const redirectToServerLoginUrl = (loginUrl) => {
window.location.href = 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', { const response = await fetch('/api/auth/me', {
method: 'GET', method: 'GET',
credentials: 'include' headers: {
Authorization: authorization
}
}); });
if (!response.ok) { if (!response.ok) {
@@ -62,3 +116,8 @@ export const getCurrentUser = async () => {
return response.json(); return response.json();
}; };
export const getCurrentUser = async () => {
return getCurrentUserByAuthorization(buildAuthorization());
};