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