274 lines
10 KiB
HTML
274 lines
10 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>OAuth客户端示例</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: 10px;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
}
|
|
.form-group {
|
|
margin-bottom: 20px;
|
|
}
|
|
label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: bold;
|
|
}
|
|
input, textarea {
|
|
width: 100%;
|
|
padding: 10px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 5px;
|
|
font-size: 14px;
|
|
}
|
|
button {
|
|
background-color: #007bff;
|
|
color: white;
|
|
padding: 12px 24px;
|
|
border: none;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
margin-right: 10px;
|
|
}
|
|
button:hover {
|
|
background-color: #0056b3;
|
|
}
|
|
button:disabled {
|
|
background-color: #ccc;
|
|
cursor: not-allowed;
|
|
}
|
|
.result {
|
|
margin-top: 20px;
|
|
padding: 15px;
|
|
border-radius: 5px;
|
|
white-space: pre-wrap;
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
}
|
|
.success {
|
|
background-color: #d4edda;
|
|
border: 1px solid #c3e6cb;
|
|
color: #155724;
|
|
}
|
|
.error {
|
|
background-color: #f8d7da;
|
|
border: 1px solid #f5c6cb;
|
|
color: #721c24;
|
|
}
|
|
.info {
|
|
background-color: #d1ecf1;
|
|
border: 1px solid #bee5eb;
|
|
color: #0c5460;
|
|
}
|
|
.step {
|
|
margin-bottom: 30px;
|
|
padding: 20px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 5px;
|
|
background-color: #f9f9f9;
|
|
}
|
|
.step h3 {
|
|
margin-top: 0;
|
|
color: #333;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>OAuth 2.0 客户端示例</h1>
|
|
<p>这个页面演示了如何使用我们的OAuth提供商进行身份验证。</p>
|
|
|
|
<div class="step">
|
|
<h3>步骤 1: 配置OAuth客户端</h3>
|
|
<div class="form-group">
|
|
<label for="clientId">客户端ID:</label>
|
|
<input type="text" id="clientId" placeholder="输入你的OAuth客户端ID">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="clientSecret">客户端密钥:</label>
|
|
<input type="text" id="clientSecret" placeholder="输入你的OAuth客户端密钥">
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="redirectUri">重定向URI:</label>
|
|
<input type="text" id="redirectUri" value="http://localhost:3001/callback" readonly>
|
|
</div>
|
|
<button onclick="testOAuthFlow()">开始OAuth流程</button>
|
|
</div>
|
|
|
|
<div class="step">
|
|
<h3>步骤 2: 获取授权码</h3>
|
|
<p>点击上面的按钮开始OAuth授权流程。你需要先登录到OAuth提供商。</p>
|
|
<div id="authResult" class="result" style="display: none;"></div>
|
|
</div>
|
|
|
|
<div class="step">
|
|
<h3>步骤 3: 交换访问令牌</h3>
|
|
<button onclick="exchangeToken()" id="exchangeBtn" disabled>交换访问令牌</button>
|
|
<div id="tokenResult" class="result" style="display: none;"></div>
|
|
</div>
|
|
|
|
<div class="step">
|
|
<h3>步骤 4: 获取用户信息</h3>
|
|
<button onclick="getUserInfo()" id="userInfoBtn" disabled>获取用户信息</button>
|
|
<div id="userInfoResult" class="result" style="display: none;"></div>
|
|
</div>
|
|
|
|
<div class="step">
|
|
<h3>步骤 5: 撤销令牌</h3>
|
|
<button onclick="revokeToken()" id="revokeBtn" disabled>撤销访问令牌</button>
|
|
<div id="revokeResult" class="result" style="display: none;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let authCode = '';
|
|
let accessToken = '';
|
|
let refreshToken = '';
|
|
|
|
async function testOAuthFlow() {
|
|
const clientId = document.getElementById('clientId').value;
|
|
const redirectUri = document.getElementById('redirectUri').value;
|
|
|
|
if (!clientId) {
|
|
showResult('authResult', '请输入客户端ID', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 构建授权URL
|
|
const authUrl = new URL('http://localhost:3000/api/oauth/authorize');
|
|
authUrl.searchParams.set('response_type', 'code');
|
|
authUrl.searchParams.set('client_id', clientId);
|
|
authUrl.searchParams.set('redirect_uri', redirectUri);
|
|
authUrl.searchParams.set('scope', 'read write');
|
|
authUrl.searchParams.set('state', 'test_state_' + Date.now());
|
|
|
|
showResult('authResult', `正在重定向到授权页面...\n${authUrl.toString()}`, 'info');
|
|
|
|
// 在实际应用中,这里会重定向到授权页面
|
|
// 为了演示,我们模拟一个授权码
|
|
authCode = 'demo_auth_code_' + Date.now();
|
|
showResult('authResult', `模拟授权成功!\n授权码: ${authCode}`, 'success');
|
|
|
|
document.getElementById('exchangeBtn').disabled = false;
|
|
} catch (error) {
|
|
showResult('authResult', `授权失败: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
async function exchangeToken() {
|
|
const clientId = document.getElementById('clientId').value;
|
|
const clientSecret = document.getElementById('clientSecret').value;
|
|
const redirectUri = document.getElementById('redirectUri').value;
|
|
|
|
if (!clientId || !clientSecret) {
|
|
showResult('tokenResult', '请输入客户端ID和密钥', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
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: authCode,
|
|
redirect_uri: redirectUri
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
accessToken = data.data.access_token;
|
|
refreshToken = data.data.refresh_token;
|
|
showResult('tokenResult', `令牌交换成功!\n访问令牌: ${accessToken.substring(0, 20)}...\n刷新令牌: ${refreshToken.substring(0, 20)}...`, 'success');
|
|
document.getElementById('userInfoBtn').disabled = false;
|
|
document.getElementById('revokeBtn').disabled = false;
|
|
} else {
|
|
showResult('tokenResult', `令牌交换失败: ${data.message}`, 'error');
|
|
}
|
|
} catch (error) {
|
|
showResult('tokenResult', `请求失败: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
async function getUserInfo() {
|
|
try {
|
|
const response = await fetch('http://localhost:3000/api/oauth/userinfo', {
|
|
headers: {
|
|
'Authorization': `Bearer ${accessToken}`
|
|
}
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showResult('userInfoResult', `用户信息获取成功!\n${JSON.stringify(data.data, null, 2)}`, 'success');
|
|
} else {
|
|
showResult('userInfoResult', `获取用户信息失败: ${data.message}`, 'error');
|
|
}
|
|
} catch (error) {
|
|
showResult('userInfoResult', `请求失败: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
async function revokeToken() {
|
|
const clientId = document.getElementById('clientId').value;
|
|
const clientSecret = document.getElementById('clientSecret').value;
|
|
|
|
try {
|
|
const response = await fetch('http://localhost:3000/api/oauth/revoke', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
body: new URLSearchParams({
|
|
token: accessToken,
|
|
client_id: clientId,
|
|
client_secret: clientSecret
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showResult('revokeResult', '令牌撤销成功!', 'success');
|
|
accessToken = '';
|
|
refreshToken = '';
|
|
document.getElementById('userInfoBtn').disabled = true;
|
|
document.getElementById('revokeBtn').disabled = true;
|
|
} else {
|
|
showResult('revokeResult', `令牌撤销失败: ${data.message}`, 'error');
|
|
}
|
|
} catch (error) {
|
|
showResult('revokeResult', `请求失败: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
function showResult(elementId, message, type) {
|
|
const element = document.getElementById(elementId);
|
|
element.textContent = message;
|
|
element.className = `result ${type}`;
|
|
element.style.display = 'block';
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |