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:
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user