在前后端分离的开发架构中,OAuth认证搭配JWT令牌已经成为主流的身份验证方案,开发者需要掌握完整的认证流程以及令牌的安全存储方法,才能保障用户身份信息安全。

OAuth认证的基本流程
OAuth是一种授权标准,允许第三方应用在用户授权的前提下获取用户的部分资源,不需要用户直接提供账号密码。在JavaScript前端场景中,常用的OAuth授权码模式流程如下:
- 用户点击登录按钮,前端跳转到授权服务器的授权页面,携带客户端ID、回调地址、请求权限范围等参数
- 用户确认授权后,授权服务器将用户重定向到前端指定的回调地址,并在URL中附带授权码
- 前端拿到授权码后,调用后端接口,将授权码传递给后端
- 后端用授权码、客户端密钥向授权服务器请求访问令牌,授权服务器返回访问令牌和刷新令牌
- 后端将令牌返回给前端,前端后续请求携带令牌即可完成身份校验
JWT令牌的结构与验证
JWT即JSON Web Token,是一种紧凑的、自包含的令牌格式,由三部分组成,分别是头部、载荷和签名,三部分之间用点分隔。
头部通常包含令牌类型alg签名算法,载荷存放用户ID、过期时间等自定义声明,签名是用密钥对前两部分进行加密生成的字符串,用于验证令牌是否被篡改。
后端拿到JWT令牌后,需要验证签名是否有效、令牌是否过期,验证通过后才允许放行请求。下面是一个Node.js环境下验证JWT的示例代码:
const jwt = require("jsonwebtoken");
// 验证JWT令牌的函数
function verifyJWT(token, secretKey) {
try {
// 验证令牌,返回解码后的载荷
const decoded = jwt.verify(token, secretKey);
return {
valid: true,
decoded: decoded
};
} catch (error) {
// 令牌无效或过期
return {
valid: false,
error: error.message
};
}
}
// 使用示例
const testToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImV4cCI6MTcxOTQ0ODAwMH0.abc123";
const secret = "your_secret_key";
const result = verifyJWT(testToken, secret);
console.log(result);
JWT令牌的安全存储方案对比
前端拿到JWT令牌后,存储方式直接影响安全性,常见的存储方案有以下几种,各有优缺点:
| 存储方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| localStorage | 存储容量大,不会随请求自动发送到服务端,操作简单 | 容易受到XSS攻击,攻击者可以通过注入的脚本获取令牌 | 对安全性要求不高的内部系统 |
| sessionStorage | 仅在当前会话有效,关闭标签页后自动清除,相对localStorage更安全 | 同样存在XSS风险,且页面刷新后数据丢失,不适合需要持久登录的场景 | 临时登录的短会话场景 |
| HTTP-only Cookie | 无法通过JavaScript脚本读取,能有效避免XSS攻击获取令牌 | 容易受到CSRF攻击,需要额外配置SameSite属性等防护措施 | 对安全性要求高的公开应用 |
推荐的安全存储实践
综合安全性考虑,推荐采用以下方案存储JWT令牌:
- 访问令牌存储在HTTP-only、SameSite=Strict的Cookie中,设置合理的过期时间,避免令牌长期有效
- 刷新令牌同样存储在HTTP-only Cookie中,过期时间可以设置得更长,用于访问令牌过期后获取新的访问令牌
- 前端请求时不需要手动携带令牌,浏览器会自动将同域下的Cookie发送到服务端,服务端从Cookie中读取令牌进行验证
- 后端需要配置CORS规则,只允许可信的域名访问接口,同时开启CSRF防护,比如验证请求头中的自定义字段
如果必须要在JavaScript中读取令牌,比如需要判断用户登录状态,可以将令牌存储在localStorage中,但需要注意做好XSS防护,对用户输入的内容进行转义,避免注入恶意脚本。同时可以在令牌中设置短过期时间,减少令牌泄露后的影响范围。
常见问题与注意事项
在实际开发中,还需要注意以下几点:
- 不要将敏感信息存放在JWT的载荷中,因为载荷部分是Base64编码的,可以被轻易解码,只是签名部分能保证内容不被篡改
- 授权码和令牌的传输必须使用HTTPS协议,避免在网络传输过程中被窃取
- 后端的签名密钥需要妥善保存,不要硬编码在代码中,最好使用环境变量配置
- 定期更换签名密钥,降低密钥泄露后的安全风险
下面是一个前端发起OAuth登录请求的示例代码:
// 跳转到OAuth授权页面
function redirectToAuth() {
const clientId = "your_client_id";
const redirectUri = encodeURIComponent("http://ipipp.com/callback");
const authUrl = `https://auth-server.com/oauth/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&scope=user_info`;
window.location.href = authUrl;
}
// 回调页面获取授权码并请求令牌
function handleCallback() {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get("code");
if (code) {
// 调用后端接口交换令牌
fetch("http://ipipp.com/api/auth/token", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
code: code
})
})
.then(response => response.json())
.then(data => {
// 根据后端返回的存储建议存储令牌
console.log("令牌获取成功", data);
})
.catch(error => {
console.error("获取令牌失败", error);
});
}
}
JavaScriptOAuthJWT安全存储修改时间:2026-06-21 14:06:24