289 lines
12 KiB
HTML
289 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>第三方应用示例</title>
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
|
<style>
|
|
body {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.app-container {
|
|
background: white;
|
|
border-radius: 15px;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.status-card {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.status-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div class="container">
|
|
<div class="row justify-content-center align-items-center min-vh-100">
|
|
<div class="col-md-8 col-lg-6">
|
|
<div class="app-container p-4">
|
|
<div class="text-center mb-4">
|
|
<i class="fas fa-rocket fa-3x text-primary mb-3"></i>
|
|
<h2 class="fw-bold">第三方应用示例</h2>
|
|
<p class="text-muted">演示OAuth 2.0授权流程</p>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card status-card h-100">
|
|
<div class="card-body text-center">
|
|
<i class="fas fa-user-lock fa-2x text-warning mb-2"></i>
|
|
<h5 class="card-title">用户状态</h5>
|
|
<p class="card-text" id="userStatus">未登录</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card status-card h-100">
|
|
<div class="card-body text-center">
|
|
<i class="fas fa-key fa-2x text-success mb-2"></i>
|
|
<h5 class="card-title">访问令牌</h5>
|
|
<p class="card-text" id="tokenStatus">未获取</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mb-3">
|
|
<div class="card-body">
|
|
<h5 class="card-title">
|
|
<i class="fas fa-cog me-2"></i>应用配置
|
|
</h5>
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<label class="form-label">客户端ID</label>
|
|
<input type="text" class="form-control" id="clientId" placeholder="输入客户端ID">
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label">客户端密钥</label>
|
|
<input type="password" class="form-control" id="clientSecret" placeholder="输入客户端密钥">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="d-grid gap-2">
|
|
<button class="btn btn-primary btn-lg" onclick="startOAuthFlow()">
|
|
<i class="fas fa-sign-in-alt me-2"></i>开始OAuth授权
|
|
</button>
|
|
<button class="btn btn-outline-secondary" onclick="getUserInfo()" id="getUserInfoBtn" disabled>
|
|
<i class="fas fa-user me-2"></i>获取用户信息
|
|
</button>
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
<div class="alert alert-info" role="alert">
|
|
<h6><i class="fas fa-info-circle me-2"></i>使用说明</h6>
|
|
<ol class="mb-0">
|
|
<li>在个人中心创建OAuth客户端</li>
|
|
<li>将客户端ID和密钥填入上方配置</li>
|
|
<li>点击"开始OAuth授权"按钮</li>
|
|
<li>在授权页面选择同意或拒绝</li>
|
|
<li>授权成功后可以获取用户信息</li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-terminal me-2"></i>API响应
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<pre id="apiResponse" class="bg-light p-3 rounded"
|
|
style="max-height: 200px; overflow-y: auto;">等待操作...</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script>
|
|
let accessToken = '';
|
|
let userInfo = null;
|
|
|
|
// 检查URL参数
|
|
function checkUrlParams() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const code = urlParams.get('code');
|
|
const error = urlParams.get('error');
|
|
const state = urlParams.get('state');
|
|
|
|
if (code) {
|
|
logResponse('收到授权码', { code, state });
|
|
exchangeCodeForToken(code);
|
|
} else if (error) {
|
|
logResponse('授权被拒绝', { error, error_description: urlParams.get('error_description'), state });
|
|
updateStatus('用户拒绝授权', 'error');
|
|
}
|
|
}
|
|
|
|
// 开始OAuth流程
|
|
function startOAuthFlow() {
|
|
const clientId = document.getElementById('clientId').value.trim();
|
|
const clientSecret = document.getElementById('clientSecret').value.trim();
|
|
|
|
if (!clientId || !clientSecret) {
|
|
alert('请输入客户端ID和密钥');
|
|
return;
|
|
}
|
|
|
|
// 保存到 localStorage
|
|
localStorage.setItem('client_id', clientId);
|
|
localStorage.setItem('client_secret', clientSecret);
|
|
|
|
const redirectUri = encodeURIComponent(window.location.origin + window.location.pathname);
|
|
const scope = encodeURIComponent('read write');
|
|
const state = 'test123';
|
|
|
|
const authUrl = `http://localhost:3001/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&state=${state}&response_type=code`;
|
|
|
|
logResponse('重定向到授权页面', { authUrl });
|
|
window.location.href = authUrl;
|
|
}
|
|
|
|
|
|
// 使用授权码交换访问令牌
|
|
async function exchangeCodeForToken(code) {
|
|
let clientId = document.getElementById('clientId').value.trim();
|
|
let clientSecret = document.getElementById('clientSecret').value.trim();
|
|
|
|
if (!clientId || !clientSecret) {
|
|
clientId = localStorage.getItem('client_id');
|
|
clientSecret = localStorage.getItem('client_secret');
|
|
}
|
|
|
|
if (!clientId || !clientSecret) {
|
|
logResponse('错误', { message: '请先配置客户端ID和密钥' });
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const redirectUri = window.location.origin + window.location.pathname;
|
|
// const redirectUri = window.location.href;
|
|
|
|
console.log(redirectUri)
|
|
const response = await fetch('http://localhost:3000/api/oauth/token', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: new URLSearchParams({
|
|
grant_type: 'authorization_code',
|
|
client_id: clientId,
|
|
client_secret: clientSecret,
|
|
code: code,
|
|
redirect_uri: redirectUri
|
|
})
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
accessToken = result.data.access_token;
|
|
updateStatus('已登录', 'success');
|
|
updateTokenStatus('已获取');
|
|
document.getElementById('getUserInfoBtn').disabled = false;
|
|
logResponse('令牌交换成功', result.data);
|
|
} else {
|
|
logResponse('令牌交换失败', result);
|
|
updateStatus('令牌获取失败', 'error');
|
|
}
|
|
} catch (error) {
|
|
logResponse('请求错误', { error: error.message });
|
|
updateStatus('网络错误', 'error');
|
|
}
|
|
}
|
|
|
|
// 获取用户信息
|
|
async function getUserInfo() {
|
|
if (!accessToken) {
|
|
logResponse('错误', { message: '没有访问令牌' });
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('http://localhost:3000/api/oauth/userinfo', {
|
|
headers: {
|
|
'Authorization': `Bearer ${accessToken}`
|
|
}
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
userInfo = result.data;
|
|
updateStatus(`已登录: ${userInfo.username}`, 'success');
|
|
logResponse('用户信息获取成功', result.data);
|
|
} else {
|
|
logResponse('用户信息获取失败', result);
|
|
}
|
|
} catch (error) {
|
|
logResponse('请求错误', { error: error.message });
|
|
}
|
|
}
|
|
|
|
// 更新状态显示
|
|
function updateStatus(status, type = 'info') {
|
|
const statusElement = document.getElementById('userStatus');
|
|
statusElement.textContent = status;
|
|
statusElement.className = `card-text text-${type === 'success' ? 'success' : type === 'error' ? 'danger' : 'warning'}`;
|
|
}
|
|
|
|
function updateTokenStatus(status) {
|
|
document.getElementById('tokenStatus').textContent = status;
|
|
}
|
|
|
|
// 记录API响应
|
|
function logResponse(title, data) {
|
|
const responseElement = document.getElementById('apiResponse');
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
const log = `[${timestamp}] ${title}:\n${JSON.stringify(data, null, 2)}\n\n`;
|
|
responseElement.textContent += log;
|
|
responseElement.scrollTop = responseElement.scrollHeight;
|
|
}
|
|
|
|
// 页面加载时检查URL参数
|
|
window.addEventListener('load', () => {
|
|
checkUrlParams();
|
|
|
|
// 从 localStorage 读取客户端ID和密钥
|
|
const savedClientId = localStorage.getItem('client_id');
|
|
const savedClientSecret = localStorage.getItem('client_secret');
|
|
|
|
if (savedClientId) {
|
|
document.getElementById('clientId').value = savedClientId;
|
|
}
|
|
|
|
if (savedClientSecret) {
|
|
document.getElementById('clientSecret').value = savedClientSecret;
|
|
}
|
|
});
|
|
|
|
</script>
|
|
</body>
|
|
|
|
</html> |