286 lines
10 KiB
HTML
286 lines
10 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>系统A - OIDC登录</title>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
max-width: 800px;
|
||
margin: 0 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);
|
||
}
|
||
.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; }
|
||
.info { background-color: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
|
||
button {
|
||
background-color: #007bff;
|
||
color: white;
|
||
border: none;
|
||
padding: 10px 20px;
|
||
border-radius: 5px;
|
||
cursor: pointer;
|
||
margin: 5px;
|
||
}
|
||
button:hover { background-color: #0056b3; }
|
||
.logout { background-color: #dc3545; }
|
||
.logout:hover { background-color: #c82333; }
|
||
.api-result {
|
||
background-color: #f8f9fa;
|
||
padding: 15px;
|
||
border-radius: 5px;
|
||
margin: 10px 0;
|
||
white-space: pre-wrap;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>系统A - OIDC登录</h1>
|
||
|
||
<div id="status" class="status info">
|
||
正在检查登录状态...
|
||
</div>
|
||
|
||
<div id="userInfo" style="display: none;">
|
||
<h2>用户信息</h2>
|
||
<div id="userDetails"></div>
|
||
<button onclick="callApi()">调用API</button>
|
||
<button class="logout" onclick="logout()">退出登录</button>
|
||
</div>
|
||
|
||
<div id="loginSection" style="display: none;">
|
||
<h2>请登录</h2>
|
||
<button onclick="login()">登录到OIDC</button>
|
||
</div>
|
||
|
||
<div id="apiResult" class="api-result" style="display: none;"></div>
|
||
</div>
|
||
|
||
<script>
|
||
// OIDC配置
|
||
const oidcConfig = {
|
||
clientId: 'a-client',
|
||
clientSecret: 'a-secret',
|
||
redirectUri: 'https://a.com/callback',
|
||
authorizationEndpoint: 'https://oidc.com/oauth2/authorize',
|
||
tokenEndpoint: 'https://oidc.com/oauth2/token',
|
||
userInfoEndpoint: 'https://oidc.com/userinfo',
|
||
scope: 'openid read'
|
||
};
|
||
|
||
// 检查URL参数中的授权码
|
||
const urlParams = new URLSearchParams(window.location.search);
|
||
const code = urlParams.get('code');
|
||
const state = urlParams.get('state');
|
||
|
||
// 页面加载时执行
|
||
window.onload = function() {
|
||
if (code) {
|
||
// 有授权码,处理回调
|
||
handleCallback(code, state);
|
||
} else {
|
||
// 检查现有token
|
||
checkAuthStatus();
|
||
}
|
||
};
|
||
|
||
// 检查认证状态
|
||
function checkAuthStatus() {
|
||
const token = localStorage.getItem('access_token');
|
||
if (token) {
|
||
// 验证token是否有效
|
||
validateToken(token);
|
||
} else {
|
||
showLoginSection();
|
||
}
|
||
}
|
||
|
||
// 验证token
|
||
function validateToken(token) {
|
||
fetch('https://oidc.com/userinfo', {
|
||
headers: {
|
||
'Authorization': `Bearer ${token}`
|
||
}
|
||
})
|
||
.then(response => {
|
||
if (response.ok) {
|
||
return response.json();
|
||
} else {
|
||
throw new Error('Token无效');
|
||
}
|
||
})
|
||
.then(userInfo => {
|
||
showUserInfo(userInfo);
|
||
})
|
||
.catch(error => {
|
||
console.error('Token验证失败:', error);
|
||
localStorage.removeItem('access_token');
|
||
localStorage.removeItem('refresh_token');
|
||
showLoginSection();
|
||
});
|
||
}
|
||
|
||
// 显示用户信息
|
||
function showUserInfo(userInfo) {
|
||
document.getElementById('status').className = 'status success';
|
||
document.getElementById('status').textContent = '登录成功!';
|
||
|
||
document.getElementById('userInfo').style.display = 'block';
|
||
document.getElementById('loginSection').style.display = 'none';
|
||
|
||
document.getElementById('userDetails').innerHTML = `
|
||
<p><strong>用户名:</strong> ${userInfo.sub || 'user'}</p>
|
||
<p><strong>Token:</strong> ${localStorage.getItem('access_token').substring(0, 50)}...</p>
|
||
`;
|
||
}
|
||
|
||
// 显示登录部分
|
||
function showLoginSection() {
|
||
document.getElementById('status').className = 'status info';
|
||
document.getElementById('status').textContent = '请登录以继续';
|
||
|
||
document.getElementById('userInfo').style.display = 'none';
|
||
document.getElementById('loginSection').style.display = 'block';
|
||
}
|
||
|
||
// 登录
|
||
function login() {
|
||
const state = generateRandomString();
|
||
localStorage.setItem('oauth_state', state);
|
||
|
||
const authUrl = new URL(oidcConfig.authorizationEndpoint);
|
||
authUrl.searchParams.set('response_type', 'code');
|
||
authUrl.searchParams.set('client_id', oidcConfig.clientId);
|
||
authUrl.searchParams.set('redirect_uri', oidcConfig.redirectUri);
|
||
authUrl.searchParams.set('scope', oidcConfig.scope);
|
||
authUrl.searchParams.set('state', state);
|
||
|
||
window.location.href = authUrl.toString();
|
||
}
|
||
|
||
// 处理回调
|
||
function handleCallback(code, state) {
|
||
const savedState = localStorage.getItem('oauth_state');
|
||
if (state !== savedState) {
|
||
document.getElementById('status').className = 'status error';
|
||
document.getElementById('status').textContent = '状态验证失败';
|
||
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 => response.json())
|
||
.then(data => {
|
||
if (data.access_token) {
|
||
localStorage.setItem('access_token', data.access_token);
|
||
if (data.refresh_token) {
|
||
localStorage.setItem('refresh_token', data.refresh_token);
|
||
}
|
||
|
||
// 获取用户信息
|
||
return fetch(oidcConfig.userInfoEndpoint, {
|
||
headers: {
|
||
'Authorization': `Bearer ${data.access_token}`
|
||
}
|
||
});
|
||
} else {
|
||
throw new Error('获取token失败: ' + JSON.stringify(data));
|
||
}
|
||
})
|
||
.then(response => response.json())
|
||
.then(userInfo => {
|
||
// 清除URL中的参数
|
||
window.history.replaceState({}, document.title, window.location.pathname);
|
||
showUserInfo(userInfo);
|
||
})
|
||
.catch(error => {
|
||
console.error('Token交换失败:', error);
|
||
document.getElementById('status').className = 'status error';
|
||
document.getElementById('status').textContent = '登录失败: ' + error.message;
|
||
});
|
||
}
|
||
|
||
// 调用API
|
||
function callApi() {
|
||
const token = localStorage.getItem('access_token');
|
||
if (!token) {
|
||
alert('请先登录');
|
||
return;
|
||
}
|
||
|
||
document.getElementById('apiResult').style.display = 'block';
|
||
document.getElementById('apiResult').textContent = '正在调用API...';
|
||
|
||
fetch('/api/hello', {
|
||
headers: {
|
||
'Authorization': `Bearer ${token}`
|
||
}
|
||
})
|
||
.then(response => {
|
||
if (response.ok) {
|
||
return response.json();
|
||
} else {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
})
|
||
.then(data => {
|
||
document.getElementById('apiResult').textContent = 'API调用成功:\n' + JSON.stringify(data, null, 2);
|
||
})
|
||
.catch(error => {
|
||
document.getElementById('apiResult').textContent = 'API调用失败:\n' + error.message;
|
||
});
|
||
}
|
||
|
||
// 退出登录
|
||
function logout() {
|
||
// 通过nginx代理调用logout
|
||
// 清理本地token
|
||
localStorage.removeItem('access_token');
|
||
localStorage.removeItem('refresh_token');
|
||
localStorage.removeItem('oauth_state');
|
||
// 跳转到Spring Security默认的退出页面(GET)
|
||
window.location.href = 'https://oidc.com/logout';
|
||
}
|
||
|
||
// 生成随机字符串
|
||
function generateRandomString() {
|
||
const array = new Uint32Array(28);
|
||
window.crypto.getRandomValues(array);
|
||
return Array.from(array, dec => ('0' + dec.toString(16)).substr(-2)).join('');
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |