前端鉴权方案选型
场景
新项目需要设计用户登录和鉴权方案,如何选择合适的认证方式?
方案对比
主流鉴权方案
| 方案 | 原理 | 适用场景 |
|---|---|---|
| Cookie + Session | 服务端存储会话,Cookie 携带 SessionID | 传统 Web、SSR |
| JWT(Token) | 无状态令牌,前端存储并携带 | SPA、微服务 |
| OAuth 2.0 | 第三方授权 | 接入微信/GitHub 登录 |
| SSO 单点登录 | 统一认证中心 | 多系统同账号 |
JWT 方案实现
auth-flow.ts
// 登录
async function login(email: string, password: string) {
const res = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
credentials: 'include', // 携带 cookie
});
const data = await res.json();
// access_token 通常通过 Set-Cookie httpOnly 设置
// 或返回在 body 中由前端存储
return data;
}
// Axios 请求拦截器 — 自动附加 Token
axios.interceptors.request.use((config) => {
const token = getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// 响应拦截器 — 401 时刷新 Token
axios.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401 && !error.config._retry) {
error.config._retry = true;
await refreshToken();
return axios(error.config);
}
return Promise.reject(error);
}
);
路由权限控制
ProtectedRoute.tsx
import { Navigate, useLocation } from 'react-router-dom';
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuth();
const location = useLocation();
if (isLoading) return <Spinner />;
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return <>{children}</>;
}
// 路由配置
const router = createBrowserRouter([
{ path: '/login', element: <LoginPage /> },
{
path: '/dashboard',
element: (
<ProtectedRoute>
<DashboardPage />
</ProtectedRoute>
),
},
]);
按钮级权限控制
Permission.tsx
function usePermission() {
const { user } = useAuth();
const permissions = user?.permissions ?? [];
return {
hasPermission: (code: string) => permissions.includes(code),
};
}
// 使用
function AdminPanel() {
const { hasPermission } = usePermission();
return (
<div>
{hasPermission('user:create') && <button>创建用户</button>}
{hasPermission('user:delete') && <button>删除用户</button>}
</div>
);
}
方案选型决策
常见面试问题
Q1: JWT 和 Session 各有什么优缺点?
答案:
| 维度 | JWT | Session |
|---|---|---|
| 存储 | 前端存储 | 服务端存储 |
| 扩展性 | 好(无状态) | 需要共享 Session |
| 安全性 | 需防 XSS 窃取 | 需防 CSRF |
| 注销 | 难以即时失效 | 删除 Session 即可 |
| 移动端 | 友好 | Cookie 支持弱 |
| 跨域 | 友好 | 需额外配置 |
Q2: Token 过期了怎么办?如何实现无感刷新?
答案:
核心方案:双 Token 机制
- Access Token:短期(15 分钟),用于接口鉴权
- Refresh Token:长期(7 天),用于刷新 Access Token
流程:Access Token 过期 → 响应拦截器拦截 401 → 用 Refresh Token 获取新 Access Token → 重发失败请求 → 对并发请求做队列排队,避免多次刷新。
详细实现见:Token 无感刷新
Q3: 前端路由权限和按钮权限怎么实现?
答案:
- 路由权限:后端返回用户可访问的路由列表 → 前端动态生成路由表 → ProtectedRoute 组件守卫
- 按钮权限:后端返回权限码列表 →
usePermissionHook 判断 → 条件渲染按钮 - 接口权限:不应只靠前端,后端必须做鉴权(前端权限只是 UX 优化)