204 lines
6.9 KiB
HTML
204 lines
6.9 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html lang="zh-CN">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<title>OIDC回调处理</title>
|
|||
|
|
<style>
|
|||
|
|
body {
|
|||
|
|
font-family: Arial, sans-serif;
|
|||
|
|
max-width: 600px;
|
|||
|
|
margin: 50px auto;
|
|||
|
|
padding: 20px;
|
|||
|
|
background-color: #f5f5f5;
|
|||
|
|
}
|
|||
|
|
.container {
|
|||
|
|
background: white;
|
|||
|
|
padding: 30px;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|||
|
|
text-align: center;
|
|||
|
|
}
|
|||
|
|
.status {
|
|||
|
|
padding: 15px;
|
|||
|
|
margin: 10px 0;
|
|||
|
|
border-radius: 5px;
|
|||
|
|
}
|
|||
|
|
.success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
|
|||
|
|
.error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
|
|||
|
|
.loading { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
|
|||
|
|
.spinner {
|
|||
|
|
border: 4px solid #f3f3f3;
|
|||
|
|
border-top: 4px solid #3498db;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
width: 40px;
|
|||
|
|
height: 40px;
|
|||
|
|
animation: spin 1s linear infinite;
|
|||
|
|
margin: 20px auto;
|
|||
|
|
}
|
|||
|
|
@keyframes spin {
|
|||
|
|
0% { transform: rotate(0deg); }
|
|||
|
|
100% { transform: rotate(360deg); }
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<div class="container">
|
|||
|
|
<h1>处理OIDC回调</h1>
|
|||
|
|
|
|||
|
|
<div id="status" class="status loading">
|
|||
|
|
<div class="spinner"></div>
|
|||
|
|
<p>正在处理认证回调...</p>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div id="result" style="display: none;">
|
|||
|
|
<div id="resultContent"></div>
|
|||
|
|
<button onclick="goToMain()" style="margin-top: 20px; padding: 10px 20px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer;">
|
|||
|
|
返回主页
|
|||
|
|
</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
// OIDC配置
|
|||
|
|
const oidcConfig = {
|
|||
|
|
clientId: 'a-client',
|
|||
|
|
clientSecret: 'a-secret',
|
|||
|
|
redirectUri: 'https://a.com/callback',
|
|||
|
|
tokenEndpoint: 'https://oidc.com/oauth2/token',
|
|||
|
|
userInfoEndpoint: 'https://oidc.com/userinfo'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 页面加载时执行
|
|||
|
|
window.onload = function() {
|
|||
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|||
|
|
const code = urlParams.get('code');
|
|||
|
|
const state = urlParams.get('state');
|
|||
|
|
const error = urlParams.get('error');
|
|||
|
|
|
|||
|
|
if (error) {
|
|||
|
|
showError('认证失败: ' + error);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!code) {
|
|||
|
|
showError('未收到授权码');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证state参数
|
|||
|
|
const savedState = localStorage.getItem('oauth_state');
|
|||
|
|
if (state !== savedState) {
|
|||
|
|
showError('状态验证失败,可能存在CSRF攻击');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 交换授权码为token
|
|||
|
|
exchangeCodeForToken(code);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 交换授权码为token
|
|||
|
|
function exchangeCodeForToken(code) {
|
|||
|
|
const tokenData = new URLSearchParams();
|
|||
|
|
tokenData.append('grant_type', 'authorization_code');
|
|||
|
|
tokenData.append('code', code);
|
|||
|
|
tokenData.append('redirect_uri', oidcConfig.redirectUri);
|
|||
|
|
|
|||
|
|
// 使用Basic认证
|
|||
|
|
const credentials = btoa(oidcConfig.clientId + ':' + oidcConfig.clientSecret);
|
|||
|
|
|
|||
|
|
fetch(oidcConfig.tokenEndpoint, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|||
|
|
'Authorization': 'Basic ' + credentials
|
|||
|
|
},
|
|||
|
|
body: tokenData
|
|||
|
|
})
|
|||
|
|
.then(response => {
|
|||
|
|
if (!response.ok) {
|
|||
|
|
return response.json().then(err => {
|
|||
|
|
throw new Error(`Token交换失败: ${err.error || response.statusText}`);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
return response.json();
|
|||
|
|
})
|
|||
|
|
.then(data => {
|
|||
|
|
if (data.access_token) {
|
|||
|
|
// 保存token
|
|||
|
|
localStorage.setItem('access_token', data.access_token);
|
|||
|
|
if (data.refresh_token) {
|
|||
|
|
localStorage.setItem('refresh_token', data.refresh_token);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 清除state
|
|||
|
|
localStorage.removeItem('oauth_state');
|
|||
|
|
|
|||
|
|
showSuccess('认证成功!正在获取用户信息...');
|
|||
|
|
|
|||
|
|
// 获取用户信息
|
|||
|
|
return fetch(oidcConfig.userInfoEndpoint, {
|
|||
|
|
headers: {
|
|||
|
|
'Authorization': `Bearer ${data.access_token}`
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
throw new Error('响应中未包含access_token');
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
.then(response => {
|
|||
|
|
if (!response.ok) {
|
|||
|
|
throw new Error('获取用户信息失败');
|
|||
|
|
}
|
|||
|
|
return response.json();
|
|||
|
|
})
|
|||
|
|
.then(userInfo => {
|
|||
|
|
showSuccess(`认证成功!欢迎 ${userInfo.sub || 'user'}`);
|
|||
|
|
})
|
|||
|
|
.catch(error => {
|
|||
|
|
console.error('认证过程出错:', error);
|
|||
|
|
showError('认证失败: ' + error.message);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示成功信息
|
|||
|
|
function showSuccess(message) {
|
|||
|
|
document.getElementById('status').className = 'status success';
|
|||
|
|
document.getElementById('status').innerHTML = `
|
|||
|
|
<h3>✅ 成功</h3>
|
|||
|
|
<p>${message}</p>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
document.getElementById('result').style.display = 'block';
|
|||
|
|
document.getElementById('resultContent').innerHTML = `
|
|||
|
|
<h3>认证完成</h3>
|
|||
|
|
<p>您已成功登录系统A</p>
|
|||
|
|
<p>Token已保存到本地存储</p>
|
|||
|
|
`;
|
|||
|
|
}, 1000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 显示错误信息
|
|||
|
|
function showError(message) {
|
|||
|
|
document.getElementById('status').className = 'status error';
|
|||
|
|
document.getElementById('status').innerHTML = `
|
|||
|
|
<h3>❌ 错误</h3>
|
|||
|
|
<p>${message}</p>
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
setTimeout(() => {
|
|||
|
|
document.getElementById('result').style.display = 'block';
|
|||
|
|
document.getElementById('resultContent').innerHTML = `
|
|||
|
|
<h3>认证失败</h3>
|
|||
|
|
<p>请检查错误信息并重试</p>
|
|||
|
|
`;
|
|||
|
|
}, 1000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 返回主页
|
|||
|
|
function goToMain() {
|
|||
|
|
window.location.href = '/';
|
|||
|
|
}
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|