单点登录(Single Sign-On,SSO)是一种身份验证机制,允许用户使用一组凭据访问多个应用程序。下面从前端角度详细讲解SSO的完整流程。
1. SSO架构概述
SSO系统通常包含三个主要组件:
-
「SSO服务器」:中央认证服务,负责用户身份验证
-
「客户端应用」:需要用户登录的各个应用
-
「用户浏览器」:用户交互界面
2. 基于Cookie的SSO实现
2.1 登录流程代码实现
// 前端应用入口组件
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false,
user: null,
isLoading: true
};
}
componentDidMount() {
// 检查用户是否已登录
this.checkLoginStatus();
}
checkLoginStatus = async () => {
try {
// 调用本地验证接口,检查是否有有效的会话
const response = await fetch('https://app1.example.com/api/auth/status', {
credentials: 'include'// 重要:包含跨域cookies
});
if (response.ok) {
const data = await response.json();
if (data.isAuthenticated) {
this.setState({
isAuthenticated: true,
user: data.user,
isLoading: false
});
return;
}
}
// 如果未登录,重定向到SSO登录页
this.redirectToSSOLogin();
} catch (error) {
console.error('验证登录状态失败:', error);
this.setState({ isLoading: false });
}
};
redirectToSSOLogin = () => {
// 当前应用URL,用于登录后重定向回来
const currentUrl = encodeURIComponent(window.location.href);
// 重定向到SSO登录页面
window.location.href = `https://sso.example.com/login?redirect=${currentUrl}`;
};
render() {
const { isAuthenticated, user, isLoading } = this.state;
if (isLoading) {
return<div>加载中...</div>;
}
if (!isAuthenticated) {
return<div>正在重定向到登录页面...</div>;
}
return (
<div>
<header>
<p>欢迎, {user.name}</p>
<button onClick={this.handleLogout}>退出登录</button>
</header>
<main>{/* 应用内容 */}</main>
</div>
);
}
handleLogout = async () => {
try {
await fetch('https://sso.example.com/logout', {
method: 'POST',
credentials: 'include'
});
// 登出后重定向到登录页
window.location.href = 'https://sso.example.com/login';
} catch (error) {
console.error('登出失败:', error);
}
};
}
2.2 SSO登录页面实现
// SSO服务器上的登录页面组件
class SSOLoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
error: null,
isLoading: false
};
}
handleInputChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
handleSubmit = async (e) => {
e.preventDefault();
this.setState({ isLoading: true, error: null });
try {
const { username, password } = this.state;
// 发送登录请求到SSO服务器
const response = await fetch('https://sso.example.com/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password }),
credentials: 'include'
});
if (!response.ok) {
const error = await response.json();
thrownewError(error.message || '登录失败');
}
// 登录成功,获取重定向URL
const urlParams = new URLSearchParams(window.location.search);
const redirectUrl = urlParams.get('redirect') || 'https://app1.example.com';
// 重定向回原应用
window.location.href = redirectUrl;
} catch (error) {
this.setState({
error: error.message,
isLoading: false
});
}
};
render() {
const { username, password, error, isLoading } = this.state;
return (
<div className="login-container">
<h2>统一登录平台</h2>
{error && <div className="error-message">{error}</div>}
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="username">用户名</label>
<input
type="text"
id="username"
name="username"
value={username}
onChange={this.handleInputChange}
required
/>
</div>
<div className="form-group">
<label htmlFor="password">密码</label>
<input
type="password"
id="password"
name="password"
value={password}
onChange={this.handleInputChange}
required
/>
</div>
<button type="submit" disabled={isLoading}>
{isLoading ? '登录中...' : '登录'}
</button>
</form>
</div>
);
}
}
3. 基于Token的SSO实现(JWT)
3.1 前端应用入口
// 使用JWT实现的SSO前端应用
class TokenBasedApp extends React.Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false,
user: null,
isLoading: true
};
}
componentDidMount() {
// 检查URL中是否有token参数(从SSO服务器重定向回来)
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('token');
if (token) {
// 保存token到localStorage
localStorage.setItem('auth_token', token);
// 清除URL中的token参数
window.history.replaceState({}, document.title, window.location.pathname);
}
// 验证token
this.validateToken();
}
validateToken = async () => {
const token = localStorage.getItem('auth_token');
if (!token) {
this.setState({ isLoading: false });
this.redirectToSSOLogin();
return;
}
try {
// 验证token有效性
const response = await fetch('https://app1.example.com/api/auth/validate', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const userData = await response.json();
this.setState({
isAuthenticated: true,
user: userData,
isLoading: false
});
} else {
// token无效,清除并重定向到登录
localStorage.removeItem('auth_token');
this.setState({ isLoading: false });
this.redirectToSSOLogin();
}
} catch (error) {
console.error('Token验证失败:', error);
this.setState({ isLoading: false });
this.redirectToSSOLogin();
}
};
redirectToSSOLogin = () => {
// 应用ID和回调URL
const appId = 'app1';
const callbackUrl = encodeURIComponent(window.location.origin);
// 重定向到SSO登录
window.location.href = `https://sso.example.com/login?appId=${appId}&callback=${callbackUrl}`;
};
handleLogout = () => {
// 清除本地token
localStorage.removeItem('auth_token');
// 重定向到SSO登出页面
const callbackUrl = encodeURIComponent(window.location.origin);
window.location.href = `https://sso.example.com/logout?callback=${callbackUrl}`;
};
render() {
const { isAuthenticated, user, isLoading } = this.state;
if (isLoading) {
return<div>加载中...</div>;
}
if (!isAuthenticated) {
return<div>正在重定向到登录页面...</div>;
}
return (
<div>
<header>
<p>欢迎, {user.name}</p>
<button onClick={this.handleLogout}>退出登录</button>
</header>
<main>{/* 应用内容 */}</main>
</div>
);
}
}
3.2 JWT登录页面
// SSO服务器上的JWT登录页面
class JWTLoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
error: null,
isLoading: false
};
}
handleInputChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
handleSubmit = async (e) => {
e.preventDefault();
this.setState({ isLoading: true, error: null });
try {
const { username, password } = this.state;
// 获取URL参数
const urlParams = new URLSearchParams(window.location.search);
const appId = urlParams.get('appId');
const callbackUrl = urlParams.get('callback');
if (!appId || !callbackUrl) {
thrownewError('缺少必要的参数');
}
// 发送登录请求
const response = await fetch('https://sso.example.com/api/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username,
password,
appId
})
});
if (!response.ok) {
const error = await response.json();
thrownewError(error.message || '登录失败');
}
// 获取JWT token
const { token } = await response.json();
// 重定向回应用,并带上token
window.location.href = `${decodeURIComponent(callbackUrl)}?token=${token}`;
} catch (error) {
this.setState({
error: error.message,
isLoading: false
});
}
};
render() {
const { username, password, error, isLoading } = this.state;
return (
<div className="login-container">
<h2>统一登录平台</h2>
{error && <div className="error-message">{error}</div>}
<form onSubmit={this.handleSubmit}>
<div className="form-group">
<label htmlFor="username">用户名</label>
<input
type="text"
id="username"
name="username"
value={username}
onChange={this.handleInputChange}
required
/>
</div>
<div className="form-group">
<label htmlFor="password">密码</label>
<input
type="password"
id="password"
name="password"
value={password}
onChange={this.handleInputChange}
required
/>
</div>
<button type="submit" disabled={isLoading}>
{isLoading ? '登录中...' : '登录'}
</button>
</form>
</div>
);
}
}
4. 使用OAuth 2.0实现SSO
4.1 前端应用OAuth流程
// 使用OAuth 2.0实现的SSO前端应用
class OAuthApp extends React.Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false,
user: null,
isLoading: true
};
// OAuth配置
this.oauthConfig = {
clientId: 'your-client-id',
redirectUri: `${window.location.origin}/callback`,
authorizationEndpoint: 'https://sso.example.com/oauth/authorize',
tokenEndpoint: 'https://sso.example.com/oauth/token',
scope: 'profile email'
};
}
componentDidMount() {
// 检查是否在OAuth回调页面
if (window.location.pathname === '/callback') {
this.handleOAuthCallback();
} else {
this.checkAuthentication();
}
}
checkAuthentication = () => {
const accessToken = localStorage.getItem('access_token');
const tokenExpiry = localStorage.getItem('token_expiry');
// 检查token是否存在且未过期
if (accessToken && tokenExpiry && newDate().getTime() < parseInt(tokenExpiry)) {
this.fetchUserInfo(accessToken);
} else {
// 清除过期token
if (accessToken) {
localStorage.removeItem('access_token');
localStorage.removeItem('token_expiry');
localStorage.removeItem('refresh_token');
}
this.setState({ isLoading: false });
}
};
fetchUserInfo = async (accessToken) => {
try {
const response = await fetch('https://sso.example.com/api/userinfo', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
if (response.ok) {
const userData = await response.json();
this.setState({
isAuthenticated: true,
user: userData,
isLoading: false
});
} else {
// token可能无效
this.setState({ isLoading: false });
this.initiateOAuthFlow();
}
} catch (error) {
console.error('获取用户信息失败:', error);
this.setState({ isLoading: false });
}
};
handleOAuthCallback = async () => {
// 从URL获取授权码
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
// 验证state防止CSRF攻击
const savedState = localStorage.getItem('oauth_state');
localStorage.removeItem('oauth_state');
if (!code || state !== savedState) {
this.setState({
isLoading: false,
error: '无效的OAuth回调'
});
return;
}
try {
// 使用授权码获取访问令牌
const tokenResponse = await fetch(this.oauthConfig.tokenEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: this.oauthConfig.redirectUri,
client_id: this.oauthConfig.clientId
})
});
if (!tokenResponse.ok) {
thrownewError('获取访问令牌失败');
}
const tokenData = await tokenResponse.json();
// 保存token
localStorage.setItem('access_token', tokenData.access_token);
localStorage.setItem('token_expiry', (newDate().getTime() + tokenData.expires_in * 1000).toString());
if (tokenData.refresh_token) {
localStorage.setItem('refresh_token', tokenData.refresh_token);
}
// 获取用户信息
awaitthis.fetchUserInfo(tokenData.access_token);
// 重定向到应用首页
window.history.replaceState({}, document.title, '/');
} catch (error) {
console.error('处理OAuth回调失败:', error);
this.setState({
isLoading: false,
error: error.message
});
}
};
initiateOAuthFlow = () => {
// 生成随机state参数防止CSRF攻击
const state = Math.random().toString(36).substring(2);
localStorage.setItem('oauth_state', state);
// 构建授权URL
const authUrl = new URL(this.oauthConfig.authorizationEndpoint);
authUrl.searchParams.append('client_id', this.oauthConfig.clientId);
authUrl.searchParams.append('redirect_uri', this.oauthConfig.redirectUri);
authUrl.searchParams.append('response_type', 'code');
authUrl.searchParams.append('scope', this.oauthConfig.scope);
authUrl.searchParams.append('state', state);
// 重定向到授权页面
window.location.href = authUrl.toString();
};
handleLogout = async () => {
// 清除本地存储的token
localStorage.removeItem('access_token');
localStorage.removeItem('token_expiry');
localStorage.removeItem('refresh_token');
// 重定向到SSO登出页面
window.location.href = `https://sso.example.com/logout?redirect_uri=${encodeURIComponent(window.location.origin)}`;
};
render() {
const { isAuthenticated, user, isLoading, error } = this.state;
if (isLoading) {
return<div>加载中...</div>;
}
if (error) {
return<div className="error-message">{error}</div>;
}
if (!isAuthenticated) {
return (
<div>
<h2>请登录以继续</h2>
<button onClick={this.initiateOAuthFlow}>使用SSO登录</button>
</div>
);
}
return (
<div>
<header>
<p>欢迎, {user.name}</p>
<button onClick={this.handleLogout}>退出登录</button>
</header>
<main>{/* 应用内容 */}</main>
</div>
);
}
}
5. 跨域问题解决方案
// 处理跨域Cookie问题的工具函数
const SSOUtils = {
// 设置跨域请求选项
getCorsOptions() {
return {
credentials: 'include',
headers: {
'Content-Type': 'application/json'
}
};
},
// 使用iframe进行跨域通信
setupIframeMessaging() {
// 创建隐藏的iframe,指向SSO域
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'https://sso.example.com/session-bridge.html';
document.body.appendChild(iframe);
returnnewPromise((resolve) => {
// 监听来自iframe的消息
window.addEventListener('message', function messageHandler(event) {
// 验证消息来源
if (event.origin !== 'https://sso.example.com') return;
// 处理会话信息
if (event.data.type === 'SESSION_INFO') {
window.removeEventListener('message', messageHandler);
resolve(event.data.payload);
}
});
});
},
// 使用JSONP解决跨域问题
fetchWithJsonp(url, callbackParam = 'callback') {
returnnewPromise((resolve, reject) => {
// 创建唯一的回调函数名
const callbackName = 'jsonp_callback_' + Math.round(100000 * Math.random());
// 创建script标签
const script = document.createElement('script');
// 设置全局回调函数
window[callbackName] = (data) => {
// 清理:删除script标签和全局回调
deletewindow[callbackName];
document.body.removeChild(script);
resolve(data);
};
// 处理错误情况
script.onerror = () => {
deletewindow[callbackName];
document.body.removeChild(script);
reject(newError('JSONP请求失败'));
};
// 构建带有回调参数的URL
const separator = url.indexOf('?') !== -1 ? '&' : '?';
script.src = `${url}${separator}${callbackParam}=${callbackName}`;
// 添加到文档中执行请求
document.body.appendChild(script);
});
}
};
6. 前端SSO会话检查组件
// 会话检查组件,可以集成到任何应用中
class SSOSessionChecker extends React.Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false,
isChecking: true
};
// 检查间隔时间(毫秒)
this.checkInterval = props.checkInterval || 5 * 60 * 1000; // 默认5分钟
this.intervalId = null;
}
componentDidMount() {
// 初始检查
this.checkSession();
// 设置定期检查
this.intervalId = setInterval(this.checkSession, this.checkInterval);
// 监听浏览器标签页激活事件,重新检查会话
document.addEventListener('visibilitychange', this.handleVisibilityChange);
}
componentWillUnmount() {
// 清理定时器和事件监听
if (this.intervalId) {
clearInterval(this.intervalId);
}
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
}
handleVisibilityChange = () => {
// 当用户切换回标签页时检查会话
if (document.visibilityState === 'visible') {
this.checkSession();
}
};
checkSession = async () => {
this.setState({ isChecking: true });
try {
// 使用图片探测技术检查SSO会话状态
// 这种方法利用了图片加载会携带cookies的特性
const timestamp = newDate().getTime();
const img = new Image();
// 创建Promise包装图片加载
const sessionCheck = newPromise((resolve, reject) => {
img.onload = () => resolve(true); // 图片加载成功,表示会话有效
img.onerror = () => resolve(false); // 图片加载失败,表示会话无效
// 5秒超时
setTimeout(() => reject(newError('会话检查超时')), 5000);
});
// 设置图片源,触发请求
img.src = `https://sso.example.com/session-check.png?t=${timestamp}`;
const isAuthenticated = await sessionCheck;
this.setState({
isAuthenticated,
isChecking: false
});
// 如果会话已失效,通知父组件
if (!isAuthenticated && this.props.onSessionExpired) {
this.props.onSessionExpired();
}
} catch (error) {
console.error('会话检查失败:', error);
this.setState({ isChecking: false });
}
};
render() {
// 将会话状态传递给子组件
returnthis.props.children({
isAuthenticated: this.state.isAuthenticated,
isChecking: this.state.isChecking,
checkSession: this.checkSession
});
}
}
7. 实现无感刷新Token
// Token自动刷新管理器
class TokenRefreshManager {
constructor(options) {
this.refreshEndpoint = options.refreshEndpoint || 'https://sso.example.com/oauth/token';
this.clientId = options.clientId;
this.tokenExpiryThreshold = options.tokenExpiryThreshold || 5 * 60 * 1000; // 默认提前5分钟刷新
this.refreshPromise = null;
}
// 初始化刷新计时器
setupRefreshTimer() {
const accessToken = localStorage.getItem('access_token');
const tokenExpiry = localStorage.getItem('token_expiry');
const refreshToken = localStorage.getItem('refresh_token');
if (!accessToken || !tokenExpiry || !refreshToken) {
return;
}
const expiresAt = parseInt(tokenExpiry);
const now = newDate().getTime();
// 计算下次刷新时间
const timeUntilRefresh = expiresAt - now - this.tokenExpiryThreshold;
if (timeUntilRefresh <= 0) {
// 如果token已经接近过期,立即刷新
this.refreshToken();
} else {
// 设置定时器,在token接近过期时刷新
setTimeout(() =>this.refreshToken(), timeUntilRefresh);
}
}
// 刷新token
refreshToken() {
// 如果已经有一个刷新请求在进行中,返回该Promise
if (this.refreshPromise) {
returnthis.refreshPromise;
}
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) {
returnPromise.reject(newError('没有可用的刷新令牌'));
}
// 创建刷新token的请求
this.refreshPromise = fetch(this.refreshEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: this.clientId
})
})
.then(response => {
if (!response.ok) {
thrownewError('刷新令牌失败');
}
return response.json();
})
.then(data => {
// 更新存储的token
localStorage.setItem('access_token', data.access_token);
localStorage.setItem('token_expiry', (newDate().getTime() + data.expires_in * 1000).toString());
if (data.refresh_token) {
localStorage.setItem('refresh_token', data.refresh_token);
}
// 设置下一次刷新的定时器
const nextRefreshTime = data.expires_in * 1000 - this.tokenExpiryThreshold;
setTimeout(() =>this.refreshToken(), nextRefreshTime);
// 返回新的token数据
return data;
})
.catch(error => {
// 刷新失败,可能需要重新登录
console.error('刷新令牌失败:', error);
// 清除无效的token
localStorage.removeItem('access_token');
localStorage.removeItem('token_expiry');
localStorage.removeItem('refresh_token');
// 触发登录流程
window.dispatchEvent(new CustomEvent('auth:required'));
throw error;
})
.finally(() => {
// 清除进行中的Promise引用
this.refreshPromise = null;
});
returnthis.refreshPromise;
}
// 获取有效的访问令牌
getAccessToken() {
const accessToken = localStorage.getItem('access_token');
const tokenExpiry = localStorage.getItem('token_expiry');
if (!accessToken || !tokenExpiry) {
// 没有token,需要登录
window.dispatchEvent(new CustomEvent('auth:required'));
returnPromise.reject(newError('未登录'));
}
const expiresAt = parseInt(tokenExpiry);
const now = newDate().getTime();
// 如果token即将过期,刷新它
if (expiresAt - now < this.tokenExpiryThreshold) {
returnthis.refreshToken().then(data => data.access_token);
}
// 返回现有的有效token
returnPromise.resolve(accessToken);
}
}
8. 前端API请求拦截器
// 使用Axios拦截器自动添加认证token
class ApiClient {
constructor() {
this.tokenManager = new TokenRefreshManager({
clientId: 'your-client-id',
refreshEndpoint: 'https://sso.example.com/oauth/token'
});
// 初始化Axios实例
this.axiosInstance = axios.create({
baseURL: 'https://api.example.com'
});
// 设置请求拦截器
this.axiosInstance.interceptors.request.use(
async (config) => {
try {
// 获取有效的访问令牌
const token = awaitthis.tokenManager.getAccessToken();
// 将token添加到请求头
config.headers.Authorization = `Bearer ${token}`;
return config;
} catch (error) {
// 获取token失败
returnPromise.reject(error);
}
},
(error) => Promise.reject(error)
);
// 设置响应拦截器
this.axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
// 检查是否是401错误(未授权)
if (error.response && error.response.status === 401) {
try {
// 尝试刷新token
awaitthis.tokenManager.refreshToken();
// 使用新token重试请求
const token = localStorage.getItem('access_token');
error.config.headers.Authorization = `Bearer ${token}`;
returnthis.axiosInstance.request(error.config);
} catch (refreshError) {
// 刷新失败,需要重新登录
window.dispatchEvent(new CustomEvent('auth:required'));
returnPromise.reject(refreshError);
}
}
returnPromise.reject(error);
}
);
// 初始化token刷新计时器
this.tokenManager.setupRefreshTimer();
}
// 封装API请求方法
get(url, config) {
returnthis.axiosInstance.get(url, config);
}
post(url, data, config) {
returnthis.axiosInstance.post(url, data, config);
}
put(url, data, config) {
returnthis.axiosInstance.put(url, data, config);
}
delete(url, config) {
returnthis.axiosInstance.delete(url, config);
}
}
9. 完整的SSO流程总结
-
「用户访问应用」:
-
前端应用检查本地存储中是否有有效的认证信息
-
如果没有,重定向到SSO登录页面
-
-
「SSO登录」:
-
用户在SSO服务器上输入凭据
-
SSO服务器验证凭据并创建会话
-
根据SSO实现方式,生成Cookie或Token
-
-
「重定向回应用」:
-
对于Cookie-based SSO:重定向回应用,带上会话Cookie
-
对于Token-based SSO:重定向回应用,带上token参数
-
对于OAuth流程:先获取授权码,然后用授权码换取访问令牌
-
-
「应用验证认证」:
-
验证Cookie或Token的有效性
-
获取用户信息
-
建立应用内的用户会话
-
-
「会话维护」:
-
定期检查SSO会话状态
-
自动刷新即将过期的Token
-
处理会话过期的情况
-
-
「单点登出」:
-
用户点击登出按钮
-
应用清除本地认证信息
-
重定向到SSO登出端点,清除SSO会话
-
SSO服务器通知所有应用登出(可选)
-
10. 安全最佳实践
// 安全增强的SSO客户端
class SecureSSO {
constructor() {
// 使用安全的存储方式
this.storage = new SecureStorage();
// CSRF保护
this.csrfToken = this.generateRandomToken();
}
// 生成随机令牌
generateRandomToken(length = 32) {
const array = newUint8Array(length);
window.crypto.getRandomValues(array);
returnArray.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
// 安全存储实现
class SecureStorage {
// 使用加密存储敏感信息
setItem(key, value) {
// 对于敏感数据,可以考虑使用Web Crypto API进行加密
const encryptedValue = this.encrypt(value);
sessionStorage.setItem(key, encryptedValue);
}
getItem(key) {
const encryptedValue = sessionStorage.getItem(key);
if (!encryptedValue) returnnull;
returnthis.decrypt(encryptedValue);
}
removeItem(key) {
sessionStorage.removeItem(key);
}
// 简单加密实现(实际应用中应使用更强的加密)
encrypt(value) {
// 这里应该使用Web Crypto API进行真正的加密
// 这只是一个示例
return btoa(value);
}
decrypt(encryptedValue) {
// 对应的解密
return atob(encryptedValue);
}
}
// 防止点击劫持
preventClickjacking() {
// 设置X-Frame-Options头(服务器端)
// 前端可以检测是否在iframe中
if (window.self !== window.top) {
// 可能在iframe中,根据策略决定是否继续
document.body.innerHTML = '为了安全,此页面不允许在iframe中显示';
}
}
// XSS防护
sanitizeInput(input) {
// 使用DOMPurify库清理用户输入
return DOMPurify.sanitize(input);
}
}
总结
单点登录(SSO)是现代Web应用中常用的身份验证机制,它允许用户使用一组凭据访问多个应用程序。从前端角度实现SSO主要有三种方式:
-
「基于Cookie的SSO」:依赖共享域的Cookie,简单但有跨域限制
-
「基于Token的SSO」:使用JWT等令牌,更灵活,适合分布式系统
-
「OAuth 2.0/OpenID Connect」:标准化的授权框架,支持第三方应用授权
无论采用哪种方式,前端实现都需要处理:
-
认证状态检查
-
登录流程
-
会话维护
-
令牌刷新
-
安全防护
-
单点登出
通过合理的架构设计和安全实践,可以构建出用户体验良好、安全可靠的单点登录系统。
3752

被折叠的 条评论
为什么被折叠?



