pin_drop当前位置:知识文库 ❯ 图文

后端通信:17. 请求认证与授权 - 从入门到实践详解

一、教程简介

在现代Web应用中,请求认证与授权是保障数据安全和用户隐私的核心机制。认证(Authentication)验证"你是谁",授权(Authorization)决定"你能做什么"。前端开发者需要理解各种认证方案的工作原理,正确地在HTTP请求中携带认证信息,并妥善处理Token的存储、刷新和过期等生命周期管理。

本教程将全面讲解HTTP认证Cookie认证Bearer Token(JWT)OAuth 2.0等主流认证方案,以及前端如何安全地管理和使用认证信息。


二、核心概念

认证 vs 授权

概念 英文 解决的问题 示例
认证 Authentication 你是谁? 用户名密码登录
授权 Authorization 你能做什么? 管理员可以删除用户
审计 Auditing 你做了什么? 操作日志记录

主流认证方案对比

方案 安全性 无状态 跨域支持 移动端友好 实现复杂度
HTTP Basic Auth
Cookie + Session 需配置 一般
Bearer Token (JWT)
OAuth 2.0

JWT(JSON Web Token)结构

代码示例

JWT = Header.Payload.Signature

Header:    {"alg": "HS256", "typ": "JWT"}          → Base64Url编码
Payload:   {"sub": "123", "name": "张三", "exp": ...} → Base64Url编码
Signature: HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

Token生命周期

代码示例

用户登录 → 服务器验证 → 返回Access Token + Refresh Token
                                    ↓
前端存储Token → 请求时携带Token → 服务器验证Token
                                    ↓
Token过期 → 使用Refresh Token获取新Token → 继续请求
                                    ↓
Refresh Token过期 → 跳转登录页 → 重新认证

三、语法与用法

HTTP认证头格式

代码示例

// Basic认证
'Authorization: Basic ' + btoa(username + ':' + password)

// Bearer Token
'Authorization: Bearer ' + accessToken

// API Key
'X-API-Key: abc123def456'

// 自定义Token
'X-Auth-Token: abc123def456'

Fetch API携带认证信息

代码示例

// 方式1:在headers中携带Token
fetch('/api/data', {
    headers: {
        'Authorization': `Bearer ${token}`
    }
});

// 方式2:携带Cookie(Session认证)
fetch('/api/data', {
    credentials: 'include'  // 携带Cookie
});

// 方式3:API Key
fetch('/api/data', {
    headers: {
        'X-API-Key': 'your-api-key'
    }
});

JWT解码

代码示例

// 解码JWT(不验证签名,仅读取内容)
function decodeJWT(token) {
    const parts = token.split('.');
    if (parts.length !== 3) throw new Error('Invalid JWT');

    const header = JSON.parse(atob(parts[0]));
    const payload = JSON.parse(atob(parts[1]));

    return { header, payload };
}

四、代码示例

示例1:认证方案演示

下面展示四种主流认证方案的完整实现,包括HTTP Basic AuthCookie + SessionBearer Token (JWT)OAuth 2.0的前端和后端代码。

代码示例

// ========== HTTP Basic Authentication ==========
// 前端实现
const username = 'admin';
const password = 'secret123';
const credentials = btoa(`${username}:${password}`);
fetch('/api/data', {
    headers: {
        'Authorization': `Basic ${credentials}`
    }
});

// 后端验证(Node.js示例)
const auth = req.headers.authorization;
if (auth && auth.startsWith('Basic ')) {
    const credentials = Buffer.from(auth.slice(6), 'base64').toString();
    const [user, pass] = credentials.split(':');
    if (user === 'admin' && pass === 'secret123') {
        // 认证通过
    }
}

代码示例

// ========== Cookie + Session ==========
// 前端登录
const loginResponse = await fetch('/api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    credentials: 'include',  // 重要:携带Cookie
    body: JSON.stringify({ username: 'admin', password: '123456' })
});

// 后续请求(Cookie自动携带)
const dataResponse = await fetch('/api/data', {
    credentials: 'include'  // 跨域时必须设置
});

// 后端实现(Node.js + Express Session)
const session = require('express-session');
app.use(session({
    secret: 'your-secret-key',
    resave: false,
    saveUninitialized: false,
    cookie: {
        httpOnly: true,     // 防止XSS读取
        secure: true,       // 仅HTTPS
        sameSite: 'lax',    // 防止CSRF
        maxAge: 86400000    // 24小时
    }
}));

代码示例

// ========== Bearer Token (JWT) ==========
// 前端实现 - AuthManager类
class AuthManager {
    constructor() {
        this.accessToken = null;
        this.refreshToken = null;
    }

    // 登录获取Token
    async login(username, password) {
        const response = await fetch('/api/auth/login', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ username, password })
        });
        const data = await response.json();
        this.accessToken = data.accessToken;
        this.refreshToken = data.refreshToken;
        this.storeTokens();
    }

    // 存储Token
    storeTokens() {
        localStorage.setItem('accessToken', this.accessToken);
        localStorage.setItem('refreshToken', this.refreshToken);
    }

    // 带认证的请求
    async authenticatedFetch(url, options = {}) {
        options.headers = {
            ...options.headers,
            'Authorization': `Bearer ${this.accessToken}`
        };
        let response = await fetch(url, options);

        // Token过期,尝试刷新
        if (response.status === 401) {
            const refreshed = await this.refreshAccessToken();
            if (refreshed) {
                options.headers['Authorization'] = `Bearer ${this.accessToken}`;
                response = await fetch(url, options);
            }
        }
        return response;
    }

    // 刷新Token
    async refreshAccessToken() {
        try {
            const response = await fetch('/api/auth/refresh', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ refreshToken: this.refreshToken })
            });
            if (response.ok) {
                const data = await response.json();
                this.accessToken = data.accessToken;
                this.storeTokens();
                return true;
            }
        } catch (e) {}
        this.logout();
        return false;
    }

    logout() {
        this.accessToken = null;
        this.refreshToken = null;
        localStorage.removeItem('accessToken');
        localStorage.removeItem('refreshToken');
    }
}

代码示例

// ========== OAuth 2.0 授权码模式 ==========
// 步骤1:重定向到授权页面
function redirectToAuth() {
    const params = new URLSearchParams({
        response_type: 'code',
        client_id: 'your-client-id',
        redirect_uri: 'https://your-app.com/callback',
        scope: 'profile email',
        state: generateRandomState()  // 防CSRF
    });
    window.location.href =
        `https://auth.example.com/authorize?${params}`;
}

// 步骤2:回调页面处理授权码
async function handleCallback() {
    const params = new URLSearchParams(window.location.search);
    const code = params.get('code');
    const state = params.get('state');

    // 验证state
    if (state !== sessionStorage.getItem('oauth_state')) {
        throw new Error('State验证失败');
    }

    // 步骤3:用授权码换取Token
    const response = await fetch('/api/auth/token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            grant_type: 'authorization_code',
            code: code,
            redirect_uri: 'https://your-app.com/callback'
        })
    });
    const tokens = await response.json();
    // 存储 tokens...
}

function generateRandomState() {
    const state = crypto.randomUUID();
    sessionStorage.setItem('oauth_state', state);
    return state;
}

示例2:JWT Token管理器

JWT Token管理器提供解码器状态监控存储安全对比功能,帮助理解JWT的内部结构和工作原理。

代码示例

// JWT解码器核心逻辑
function decodeJWT() {
    const token = document.getElementById('jwtInput').value.trim();
    if (!token) return;

    const parts = token.split('.');
    if (parts.length !== 3) throw new Error('无效的JWT格式');

    // 解码Header
    const header = JSON.parse(atob(parts[0].replace(/-/g, '+').replace(/_/g, '/')));
    // 解码Payload
    const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));

    // 标准声明说明
    const claims = {
        iss: { desc: '签发者', value: payload.iss },
        sub: { desc: '主题(用户ID)', value: payload.sub },
        aud: { desc: '接收者', value: payload.aud },
        exp: { desc: '过期时间', value: payload.exp ? new Date(payload.exp * 1000).toLocaleString() : '-' },
        nbf: { desc: '生效时间', value: payload.nbf ? new Date(payload.nbf * 1000).toLocaleString() : '-' },
        iat: { desc: '签发时间', value: payload.iat ? new Date(payload.iat * 1000).toLocaleString() : '-' },
        jti: { desc: 'JWT ID', value: payload.jti },
    };
    // 显示解码结果...
}

小贴士:Token存储安全对比

localStorage:XSS风险高,CSRF风险低,持久存储,推荐度一般
sessionStorage:XSS风险高,CSRF风险低,会话级存储,推荐度一般
Cookie(httpOnly):XSS风险低,CSRF风险高,可配置持久性,推荐
内存(变量):XSS风险低,CSRF风险低,刷新丢失,最安全

示例3:Token刷新与请求拦截

双Token方案中,Access Token短期有效(如15分钟),Refresh Token长期有效(如7天)。刷新策略包括主动刷新、被动刷新和队列刷新。

代码示例

// Token刷新机制核心逻辑
class TokenManager {
    constructor() {
        this.accessToken = null;
        this.refreshToken = null;
        this.isRefreshing = false;
        this.refreshSubscribers = [];  // 等待刷新完成的请求队列
        this.refreshTimer = null;
    }

    // 安排自动刷新(在过期前5分钟刷新)
    scheduleRefresh() {
        if (this.refreshTimer) clearTimeout(this.refreshTimer);
        const payload = this.decodeToken(this.accessToken);
        if (!payload || !payload.exp) return;

        const expiresIn = payload.exp * 1000 - Date.now();
        const refreshAt = expiresIn - 5 * 60 * 1000;  // 提前5分钟

        if (refreshAt > 0) {
            this.refreshTimer = setTimeout(() => {
                this.refreshAccessToken();
            }, refreshAt);
        }
    }

    // 刷新Access Token
    async refreshAccessToken() {
        if (this.isRefreshing) {
            // 已在刷新中,加入等待队列
            return new Promise(resolve => {
                this.refreshSubscribers.push(resolve);
            });
        }
        this.isRefreshing = true;

        try {
            const response = await fetch('/api/auth/refresh', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ refreshToken: this.refreshToken })
            });
            if (!response.ok) throw new Error('刷新失败');

            const data = await response.json();
            this.accessToken = data.accessToken;
            if (data.refreshToken) {
                this.refreshToken = data.refreshToken;
            }
            this.scheduleRefresh();

            // 通知所有等待的请求
            this.refreshSubscribers.forEach(cb => cb(this.accessToken));
            this.refreshSubscribers = [];
            return this.accessToken;
        } catch (error) {
            this.clearTokens();
            window.location.href = '/login';
            throw error;
        } finally {
            this.isRefreshing = false;
        }
    }
}

代码示例

// 请求/响应拦截器(类Axios实现)
class HttpClient {
    constructor(baseURL, tokenManager) {
        this.baseURL = baseURL;
        this.tokenManager = tokenManager;
        this.requestInterceptors = [];
        this.responseInterceptors = [];
    }

    addRequestInterceptor(onFulfilled, onRejected) {
        this.requestInterceptors.push({ onFulfilled, onRejected });
    }

    addResponseInterceptor(onFulfilled, onRejected) {
        this.responseInterceptors.push({ onFulfilled, onRejected });
    }

    // 核心请求方法
    async request(url, options = {}) {
        let config = {
            url: this.baseURL + url,
            ...options,
            headers: {
                'Content-Type': 'application/json',
                ...options.headers
            }
        };
        config = await this.runRequestInterceptors(config);
        let response = await fetch(config.url, config);
        response = await this.runResponseInterceptors(response);
        return response;
    }
}

// 使用示例
const tokenManager = new TokenManager();
const http = new HttpClient('https://api.example.com', tokenManager);

// 请求拦截器:添加Token
http.addRequestInterceptor((config) => {
    if (tokenManager.accessToken) {
        config.headers['Authorization'] = `Bearer ${tokenManager.accessToken}`;
    }
    return config;
});

// 响应拦截器:处理401
http.addResponseInterceptor(
    async (response) => {
        if (response.status === 401) {
            const newToken = await tokenManager.refreshAccessToken();
            response.config.headers['Authorization'] = `Bearer ${newToken}`;
            return fetch(response.config.url, response.config);
        }
        return response;
    },
    (error) => {
        console.error('请求失败:', error);
        throw error;
    }
);

示例4:权限控制与路由守卫

RBAC(Role-Based Access Control)基于角色的访问控制,是最常用的权限模型。通过定义角色与权限的映射关系,实现灵活的权限管理。

功能/权限 游客(guest) 用户(user) 编辑(editor) 管理员(admin)
查看文章
发表评论
发布文章
删除文章
管理用户
系统设置

代码示例

// 权限定义
const PERMISSIONS = {
    'article:read':   ['guest', 'user', 'editor', 'admin'],
    'comment:write':  ['user', 'editor', 'admin'],
    'article:write':  ['editor', 'admin'],
    'article:delete': ['admin'],
    'user:manage':    ['admin'],
    'system:config':  ['admin']
};

// 权限检查
function hasPermission(role, permission) {
    const allowedRoles = PERMISSIONS[permission];
    return allowedRoles ? allowedRoles.includes(role) : false;
}

// 路由守卫
const routeGuard = {
    requireAuth(to, from, next) {
        const token = getToken();
        if (!token) {
            next({ path: '/login', query: { redirect: to.fullPath } });
            return;
        }
        next();
    },
    requirePermission(permission) {
        return (to, from, next) => {
            const user = getCurrentUser();
            if (!user) { next({ path: '/login' }); return; }
            if (!hasPermission(user.role, permission)) {
                next({ path: '/403' });
                return;
            }
            next();
        };
    }
};

// 全局前置守卫
router.beforeEach((to, from, next) => {
    const publicPages = ['/login', '/register', '/'];
    const authRequired = !publicPages.includes(to.path);
    if (authRequired && !getToken()) {
        next({ path: '/login', query: { redirect: to.fullPath } });
    } else {
        next();
    }
});

// 按钮级权限控制(Vue指令)
Vue.directive('permission', {
    inserted(el, binding) {
        const user = getCurrentUser();
        if (!user || !hasPermission(user.role, binding.value)) {
            el.parentNode && el.parentNode.removeChild(el);
        }
    }
});
// 使用: <button v-permission="'article:delete'">删除</button>

五、浏览器兼容性

特性 Chrome Firefox Safari Edge IE
Authorization头 全部 全部 全部 全部 全部
Fetch credentials 42+ 39+ 10.1+ 14+ 不支持
XHR withCredentials 全部 全部 全部 全部 10+
localStorage 4+ 3.5+ 4+ 12+ 8+
sessionStorage 5+ 2+ 4+ 12+ 8+
crypto.subtle 37+ 34+ 7+ 12+ 不支持
btoa/atob 4+ 2+ 3+ 12+ 10+

六、注意事项与最佳实践

1. Token安全存储

代码示例

// 推荐方案:内存存储 + 持久化Refresh Token
class SecureTokenStorage {
    constructor() {
        this.accessToken = null;  // 内存中,最安全
        this.refreshTokenKey = 'refresh_token';
    }

    setAccessToken(token) {
        this.accessToken = token;
        // 不持久化Access Token
    }

    setRefreshToken(token) {
        // Refresh Token可以持久化在httpOnly Cookie中
        // 或加密后存储在localStorage
        if (token) {
            const encrypted = this.encrypt(token);
            localStorage.setItem(this.refreshTokenKey, encrypted);
        }
    }

    getAccessToken() {
        return this.accessToken;
    }

    getRefreshToken() {
        const encrypted = localStorage.getItem(this.refreshTokenKey);
        return encrypted ? this.decrypt(encrypted) : null;
    }

    clear() {
        this.accessToken = null;
        localStorage.removeItem(this.refreshTokenKey);
    }

    encrypt(text) {
        return btoa(encodeURIComponent(text));
    }

    decrypt(text) {
        return decodeURIComponent(atob(text));
    }
}

2. 防止XSS窃取Token

代码示例

// 1. 使用httpOnly Cookie存储Token(前端无法通过JS读取)
// 后端设置:
// Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict

// 2. Content Security Policy
// Content-Security-Policy: default-src 'self'; script-src 'self'

// 3. 输入消毒
function sanitizeInput(input) {
    const div = document.createElement('div');
    div.textContent = input;
    return div.innerHTML;
}

// 4. 避免在URL中传递Token
// 错误:https://api.example.com/data?token=xxx
// 正确:在Authorization头中传递

提示:永远不要在URL参数中传递Token,因为URL会被记录在浏览器历史、服务器日志和代理日志中。应该始终使用Authorization请求头传递。

3. CSRF防护

代码示例

// 方案1:CSRF Token(Cookie认证时必须)
// 后端生成CSRF Token,前端在请求中携带
const csrfToken = document.querySelector('meta[name="csrf-token"]').content;
fetch('/api/data', {
    method: 'POST',
    headers: {
        'X-CSRF-Token': csrfToken,
        'Content-Type': 'application/json'
    },
    credentials: 'include',
    body: JSON.stringify(data)
});

// 方案2:使用Bearer Token代替Cookie(天然防CSRF)
// Token在Authorization头中传递,不通过Cookie自动发送

// 方案3:SameSite Cookie属性
// Set-Cookie: sid=xxx; SameSite=Strict; Secure; HttpOnly

七、代码规范示例

规范的认证模块

一个规范的认证模块应该统一管理Token的存储、刷新和请求认证,提供清晰的API接口,并妥善处理各种边界情况。

代码示例

/**
 * 认证管理模块
 * 统一管理Token的存储、刷新和请求认证
 */
class AuthModule {
    constructor(config = {}) {
        this.loginUrl = config.loginUrl || '/api/auth/login';
        this.refreshUrl = config.refreshUrl || '/api/auth/refresh';
        this.logoutUrl = config.logoutUrl || '/api/auth/logout';
        this.onAuthChange = config.onAuthChange || (() => {});
        this.onTokenExpired = config.onTokenExpired || (() => {});

        this._accessToken = null;
        this._refreshToken = null;
        this._user = null;
        this._refreshTimer = null;
        this._isRefreshing = false;
        this._pendingRequests = [];

        this._loadFromStorage();
    }

    get isAuthenticated() {
        return !!this._accessToken;
    }

    get user() {
        return this._user;
    }

    // 登录
    async login(credentials) {
        const response = await fetch(this.loginUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(credentials)
        });
        if (!response.ok) {
            const error = await response.json();
            throw new Error(error.message || '登录失败');
        }
        const data = await response.json();
        this._setTokens(data.accessToken, data.refreshToken);
        this._user = data.user;
        this._saveToStorage();
        this._scheduleRefresh();
        this.onAuthChange(true);
        return data;
    }

    // 带认证的请求
    async fetch(url, options = {}) {
        options.headers = {
            ...options.headers,
            ...this._getAuthHeaders()
        };
        let response = await fetch(url, options);

        if (response.status === 401) {
            const newToken = await this._refreshAccessToken();
            if (newToken) {
                options.headers = {
                    ...options.headers,
                    ...this._getAuthHeaders()
                };
                response = await fetch(url, options);
            } else {
                this.onTokenExpired();
                throw new Error('认证已过期,请重新登录');
            }
        }
        return response;
    }

    // 刷新Token(含并发请求队列处理)
    async _refreshAccessToken() {
        if (this._isRefreshing) {
            return new Promise(resolve => {
                this._pendingRequests.push(resolve);
            });
        }
        this._isRefreshing = true;
        try {
            const response = await fetch(this.refreshUrl, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ refreshToken: this._refreshToken })
            });
            if (!response.ok) throw new Error('刷新失败');
            const data = await response.json();
            this._setTokens(data.accessToken, data.refreshToken || this._refreshToken);
            this._saveToStorage();
            this._scheduleRefresh();
            this._pendingRequests.forEach(cb => cb(this._accessToken));
            this._pendingRequests = [];
            return this._accessToken;
        } catch (error) {
            this._clearTokens();
            this._pendingRequests.forEach(cb => cb(null));
            this._pendingRequests = [];
            return null;
        } finally {
            this._isRefreshing = false;
        }
    }

    _setTokens(accessToken, refreshToken) {
        this._accessToken = accessToken;
        this._refreshToken = refreshToken;
    }

    _getAuthHeaders() {
        const headers = {};
        if (this._accessToken) {
            headers['Authorization'] = `Bearer ${this._accessToken}`;
        }
        return headers;
    }

    _scheduleRefresh() {
        if (this._refreshTimer) clearTimeout(this._refreshTimer);
        const payload = this._decodeToken(this._accessToken);
        if (!payload || !payload.exp) return;
        const expiresIn = payload.exp * 1000 - Date.now();
        const refreshAt = Math.max(expiresIn - 5 * 60 * 1000, 0);
        this._refreshTimer = setTimeout(() => {
            this._refreshAccessToken();
        }, refreshAt);
    }

    _decodeToken(token) {
        try {
            return JSON.parse(atob(token.split('.')[1]));
        } catch { return null; }
    }

    _saveToStorage() {
        if (this._refreshToken) {
            sessionStorage.setItem('auth_refresh', this._refreshToken);
        }
        if (this._user) {
            sessionStorage.setItem('auth_user', JSON.stringify(this._user));
        }
    }

    _loadFromStorage() {
        this._refreshToken = sessionStorage.getItem('auth_refresh');
        const userStr = sessionStorage.getItem('auth_user');
        if (userStr) {
            try { this._user = JSON.parse(userStr); } catch {}
        }
    }

    _clearTokens() {
        this._accessToken = null;
        this._refreshToken = null;
        this._user = null;
        if (this._refreshTimer) clearTimeout(this._refreshTimer);
        sessionStorage.removeItem('auth_refresh');
        sessionStorage.removeItem('auth_user');
    }
}

八、常见问题与解决方案

问题1:Token过期后用户操作丢失

代码示例

// 问题:用户填写长表单后提交,Token过期导致401,刷新后表单数据丢失

// 解决方案:在请求拦截器中保存请求配置,刷新后自动重试
async function safeFetch(url, options) {
    let response = await fetch(url, options);

    if (response.status === 401) {
        const newToken = await refreshToken();
        if (newToken) {
            // 重试原请求(包括请求体)
            options.headers['Authorization'] = `Bearer ${newToken}`;
            response = await fetch(url, options);
        }
    }

    return response;
}

问题2:多标签页Token同步

代码示例

// 问题:标签页A刷新了Token,标签页B还在使用旧Token

// 解决方案1:监听storage事件
window.addEventListener('storage', (event) => {
    if (event.key === 'auth_token_updated') {
        // 从存储中重新加载Token
        authModule._loadFromStorage();
    }
});

// 更新Token时通知其他标签页
function updateToken(token) {
    localStorage.setItem('auth_token', token);
    localStorage.setItem('auth_token_updated', Date.now().toString());
}

// 解决方案2:使用BroadcastChannel
const channel = new BroadcastChannel('auth_channel');
channel.onmessage = (event) => {
    if (event.data.type === 'TOKEN_REFRESHED') {
        authModule._accessToken = event.data.token;
    }
};

// 刷新Token后广播
function broadcastTokenRefresh(token) {
    channel.postMessage({ type: 'TOKEN_REFRESHED', token });
}

问题3:并发请求导致多次刷新

代码示例

// 问题:多个请求同时401,触发多次Token刷新

// 解决方案:使用请求队列,只刷新一次
let isRefreshing = false;
let pendingRequests = [];

async function handleUnauthorized(originalRequest) {
    if (isRefreshing) {
        return new Promise(resolve => {
            pendingRequests.push(token => {
                originalRequest.headers['Authorization'] = `Bearer ${token}`;
                resolve(fetch(originalRequest));
            });
        });
    }

    isRefreshing = true;

    try {
        const newToken = await refreshToken();
        pendingRequests.forEach(cb => cb(newToken));
        pendingRequests = [];
        return newToken;
    } catch (error) {
        pendingRequests = [];
        throw error;
    } finally {
        isRefreshing = false;
    }
}

九、总结

请求认证与授权是Web应用安全的核心,本教程涵盖了以下内容:

  • 认证方案对比:理解HTTP Basic Auth、Cookie+Session、Bearer Token(JWT)、OAuth 2.0等方案的工作原理、优缺点和适用场景

  • JWT深入理解:掌握JWT的结构(Header、Payload、Signature)、标准声明(iss、sub、exp、iat等)、编解码方法和安全注意事项

  • Token管理:学会Token的安全存储策略、自动刷新机制、请求拦截器的实现,以及并发请求时的Token刷新队列

  • 权限控制:理解RBAC权限模型,实现路由守卫和按钮级权限控制,确保前端权限与后端权限一致

  • 安全防护:掌握XSS防护、CSRF防护、Token安全存储等安全最佳实践,构建安全的认证体系

  • 生产实践:了解Token过期处理、多标签页同步、并发刷新等实际问题的解决方案


常见问题

认证和授权有什么区别?

认证(Authentication)验证"你是谁",例如通过用户名密码登录确认用户身份。授权(Authorization)决定"你能做什么",例如管理员可以删除用户而普通用户不能。认证是授权的前提,先确认身份再分配权限。

JWT和Session哪个更适合现代Web应用?

JWT(Bearer Token)更适合现代Web应用,因为它是无状态的,天然支持分布式系统和跨域场景,移动端友好。Session需要在服务器端存储状态,在微服务架构中需要额外的Session共享方案。但对于传统Web应用,Cookie+Session实现更简单。

Token应该存储在哪里最安全?

最安全的方案是Access Token存储在内存中(JavaScript变量),Refresh Token存储在httpOnly Cookie中。localStorage和sessionStorage容易被XSS攻击窃取。如果必须使用localStorage,应加密存储并设置较短的Token有效期。

如何处理Token过期的情况?

推荐使用双Token方案:Access Token短期有效(15分钟),Refresh Token长期有效(7天)。收到401响应时,使用Refresh Token获取新的Access Token并重试原请求。应实现请求队列机制,避免并发请求导致多次刷新。还可以在Token即将过期前主动刷新。

多标签页之间如何同步Token?

有两种方案:一是监听storage事件,当一个标签页更新Token时通知其他标签页重新加载;二是使用BroadcastChannel API进行标签页间通信,刷新Token后广播通知所有标签页更新。

OAuth 2.0适用于什么场景?

OAuth 2.0适用于第三方应用授权场景,如"使用微信/Google登录"。它允许用户授权第三方应用访问其在其他服务上的信息,而无需将密码提供给第三方。实现复杂度较高,但对于需要跨平台授权的场景是标准解决方案。

标签: 请求认证 JWT令牌 Token管理 OAuth2.0 权限控制 安全防护 Session认证

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 后端通信:16. 跨域请求与CORS - 从入门到实践详解 下一篇: 后端通信:18. 请求缓存策略 - 从入门到实践详解

poll相关推荐