Files

143 lines
6.0 KiB
HTML
Raw Permalink Normal View History

<!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>