oidc-gateway-demo/resourceservicehtmlb/index.html

294 lines
10 KiB
HTML
Raw Normal View History

2025-07-18 17:08:09 +08:00
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>系统B - 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>系统B - 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: 'b-client',
clientSecret: 'b-secret',
redirectUri: 'https://b.local.com/callback',
authorizationEndpoint: 'https://oidc.local.com/oauth2/authorize',
tokenEndpoint: 'https://oidc.local.com/oauth2/token',
userInfoEndpoint: 'https://oidc.local.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.local.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);
2025-07-19 14:00:11 +08:00
window.location.href = authUrl.toString();
2025-07-18 17:08:09 +08:00
}
// 处理回调
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() {
// 清理本地token
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('oauth_state');
// 获取id_token如果有
const idToken = localStorage.getItem('id_token'); // 登录时保存id_token
// 退出后跳转到首页
const redirectUri = encodeURIComponent('https://b.local.com');
// 拼接logout url
let logoutUrl = `https://b.local.com/oidc-logout?post_logout_redirect_uri=${redirectUri}`;
if (idToken) {
logoutUrl += `&id_token_hint=${idToken}`;
}
// 跳转到OIDC logout端点
window.location.href = logoutUrl;
}
// 生成随机字符串
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>