oidc-gateway-demo/resourceservicehtml/index.html

294 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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.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);
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() {
// 清理本地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://a.local.com');
// 拼接logout url
let logoutUrl = `https://a.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>