5.3 登录、认证与权限
本章目标: 知道登录状态如何进入页面,理解前端控制体验与后端保障权限的边界。本章是方案说明,附带任务 API 不包含登录接口。
两种常见方案
| 方案 | 浏览器怎么带凭据 | 适用时的重点 |
|---|---|---|
| Cookie 会话 | 浏览器按 Cookie 规则自动发送 | HttpOnly、Secure、SameSite、CSRF 防护 |
| Bearer access token | 客户端加 Authorization 头 | token 获取/过期/刷新、存储与 XSS 风险 |
同源的后台管理应用通常可以优先评估 Cookie/BFF 方案。对接身份平台、移动客户端或独立 API 时,根据现有认证架构采用 OAuth/OIDC 等成熟流程。
不要为学习页面自己发明 token 签名和身份协议。ASP.NET Core 已有认证授权中间件,成熟身份平台也有配套客户端。
登录页面需要的状态
登录流程通常包含:检查当前会话、未登录、提交登录、已登录、凭据过期。至少区分“正在检查”与“确实未登录”,否则刷新页面可能先误跳登录页。
登录失败保留用户名,密码按产品要求清理。错误信息避免泄露账号是否存在。退出时清理前端身份缓存,并让服务端会话或刷新凭据失效。
Cookie 请求与 CSRF
同源 fetch 默认会带同源 Cookie。HttpOnly Cookie 不能由 JS 读取,但浏览器仍会自动发送,因此HttpOnly 不等于自动解决 CSRF。
有状态 Cookie 认证的写入请求应按服务端方案携带防伪 token,结合 SameSite 和 origin 检查等措施。跨域凭据配置见CORS 章节。
Bearer 请求片段
以下仅展示“已有合法 access token 后如何发送”,不实现登录或存储:
async function getCurrentUser(accessToken: string) {
const response = await fetch('/api/me', {
headers: { Authorization: `Bearer ${accessToken}` },
});
if (response.status === 401) throw new Error('登录已过期');
if (response.status === 403) throw new Error('没有访问权限');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
}
JS 可访问的 token 存在 XSS 风险。localStorage 不会因为它叫“本地存储”就自动安全;存储方式必须与身份方案一起设计。前端包中不能放客户端密钥或 JWT 签名密钥。
路由守卫负责导航体验
router.beforeEach(async to => {
await session.ensureLoaded();
if (to.meta.requiresAuth && !session.isAuthenticated) {
return { name: 'login', query: { redirect: to.fullPath } };
}
});
这里的 router、session 和 login 路由需要按实际项目实现,不属于任务示例的现成接口。ensureLoaded 应协调并发检查和失败状态,避免每次跳转都重复请求。
登录后处理 redirect 时仅允许站内合法路径,避免开放重定向。
用户可以绕过 UI 直接请求 API。服务端必须对每个敏感接口及资源执行授权,例如 [Authorize]、Minimal API 的 .RequireAuthorization() 和任务归属检查。
401、403 与重试
401 通常表示未认证或凭据失效;403 表示已确认身份但权限不足。不要把所有 403 都处理为重新登录。
刷新 token 的流程需要协调并发、限制次数并防止无限重试。写入请求也不能无条件重放,因为服务端可能已经执行操作。学习阶段先用现有身份方案完成明确的过期提示。
练习
画出“访问受保护页面 → 检查会话 → 登录 → 返回页面 → API 授权”的流程,并指出哪一步真正保障数据权限。
参考答案
API 的服务端授权与资源归属检查保障数据权限。路由守卫和按钮可见性提供交互体验;即使整个前端被绕过,API 仍应拒绝无权请求。