Files
2026-03-18 15:18:41 +08:00

143 lines
6.0 KiB
HTML
Raw Permalink 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">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>鉴权实验室 - Spring Boot</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; background:#f5f5f5; }
.container { max-width: 1100px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg,#4facfe,#00f2fe); color:#fff; padding:28px 20px; border-radius:12px; margin-bottom:20px; text-align:center; }
.nav { display:flex; gap:10px; flex-wrap:wrap; justify-content:center; margin-bottom:20px; }
.nav a { padding:10px 18px; background:#fff; border-radius:20px; text-decoration:none; color:#333; }
.nav a.active { background:#4facfe; color:#fff; }
.card { background:#fff; border-radius:12px; padding:20px; margin-bottom:20px; box-shadow:0 2px 10px rgba(0,0,0,.08); }
.card h3 { color:#1890ff; margin-bottom:12px; }
.lab { background:#fff7e6; border-left:4px solid #fa8c16; padding:15px; border-radius:8px; margin-bottom:20px; }
.row { display:flex; gap:10px; flex-wrap:wrap; margin:10px 0; }
input, select { padding:10px; border:1px solid #ddd; border-radius:6px; min-width:200px; }
button { padding:10px 16px; background:#1890ff; color:#fff; border:none; border-radius:6px; cursor:pointer; }
button.secondary { background:#6c757d; }
.result { background:#111827; color:#d1d5db; padding:14px; border-radius:8px; min-height:120px; white-space:pre-wrap; font-family:monospace; }
.hint { color:#666; margin-top:8px; line-height:1.7; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔐 Spring 鉴权实验室</h1>
<p>登录 → 取 Token → 访问受保护接口 → 对比 learn/advanced 模式</p>
</div>
<div class="nav">
<a href="index.html">🏠 首页</a>
<a href="advanced.html">🚀 高级功能</a>
<a href="verify-lab.html">🩺 修复验证实验室</a>
<a href="api.html">🔌 API 测试</a>
<a href="auth-lab.html" class="active">🔐 鉴权实验室</a>
</div>
<div class="lab">
<strong>实验任务卡</strong>
<ul style="padding-left:20px; line-height:1.8; margin-top:8px;">
<li>步骤1点击“读取当前模式”确认现在是 learn / advanced</li>
<li>步骤2用 admin/admin123 登录,拿到 token</li>
<li>步骤3带上 token 调用当前实验接口,观察是否放行或返回 503/401</li>
<li>步骤4切换 auth.type 后对比 none/jwt/satoken 行为差异</li>
</ul>
</div>
<div class="card">
<h3>当前模式</h3>
<div class="row">
<button onclick="loadProfile()">读取当前模式</button>
</div>
<div id="profileResult" class="result">点击按钮读取...</div>
</div>
<div class="card">
<h3>登录拿 Token</h3>
<div class="row">
<input id="username" value="admin" placeholder="用户名">
<input id="password" value="admin123" placeholder="密码" type="password">
<button onclick="login()">登录</button>
</div>
<div class="hint">演示账号admin/admin123 或 user/user123</div>
<div id="loginResult" class="result">点击登录后显示响应...</div>
</div>
<div class="card">
<h3>接口访问实验</h3>
<div class="row">
<button onclick="callSecureApi()">访问 /api/auth/info</button>
<button class="secondary" onclick="clearToken()">清空本地 Token</button>
</div>
<div class="hint">当前页面会优先从 localStorage 读取 token 并自动加到 Authorization 头里。learn 模式下接口通常直接放行advanced+jwt 下更适合观察真实鉴权链路。</div>
<div id="secureResult" class="result">点击按钮后显示接口结果...</div>
</div>
</div>
<script>
let cachedToken = localStorage.getItem('spring.scaffold.token') || '';
async function loadProfile() {
const el = document.getElementById('profileResult');
el.textContent = '读取中...';
try {
const res = await fetch('/api/profile');
const data = await res.json();
el.textContent = JSON.stringify(data, null, 2);
} catch (e) {
el.textContent = '读取失败: ' + e.message;
}
}
async function login() {
const el = document.getElementById('loginResult');
el.textContent = '登录中...';
try {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: document.getElementById('username').value,
password: document.getElementById('password').value,
})
});
const data = await res.json();
const token = data.token || data.data?.token || '';
if (token) {
cachedToken = token;
localStorage.setItem('spring.scaffold.token', token);
}
el.textContent = JSON.stringify(data, null, 2);
} catch (e) {
el.textContent = '登录失败: ' + e.message;
}
}
async function callSecureApi() {
const el = document.getElementById('secureResult');
el.textContent = '请求中...';
try {
const headers = {};
if (cachedToken) headers['Authorization'] = 'Bearer ' + cachedToken;
const res = await fetch('/api/auth/info', { headers });
const text = await res.text();
el.textContent = `HTTP ${res.status}\n\n${text}`;
} catch (e) {
el.textContent = '请求失败: ' + e.message;
}
}
function clearToken() {
cachedToken = '';
localStorage.removeItem('spring.scaffold.token');
document.getElementById('secureResult').textContent = '本地 token 已清空';
}
loadProfile();
</script>
</body>
</html>