143 lines
6.0 KiB
HTML
143 lines
6.0 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>鉴权实验室 - 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>
|