v.0.2.0-beta
目前oauth已经可以正常使用
This commit is contained in:
289
public/third-party-app.html
Normal file
289
public/third-party-app.html
Normal file
@ -0,0 +1,289 @@
|
||||
<!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>
|
Reference in New Issue
Block a user