不再用localStorage:2025年用Cookie管理JWT令牌的最佳方法

更新日期: 2025-08-27阅读: 90标签: Cookie

很多开发者习惯把JWT令牌存在localStorage里,但这样做其实有安全风险。如果网站存在XSS漏洞,攻击者可以轻易偷走令牌。换个方法,把令牌放在HttpOnly Cookie里,能解决这个问题。

HttpOnly Cookie有三个好处:

  1. JavaScript无法读取,XSS攻击偷不走令牌

  2. 浏览器自动在每次请求中携带,不需要手动设置请求头

  3. 可以使用安全标志,降低CSRF和窃听风险

只需要一些配置,就能大幅提升安全性。


服务器端配置:Node.js中签发和刷新令牌

先搭建一个简单的Express服务器:

// server/index.mjs
import express from 'express';
import cookieParser from 'cookie-parser';
import csurf from 'csurf';

const app = express();
app.use(express.json());
app.use(cookieParser());

// 设置CSRF保护
app.use(csurf({ cookie: true }));

// 获取CSRF令牌的接口
app.get('/api/csrf-token', (req, res) => {
  res.setHeader('x-csrf-token', req.csrfToken());
  res.sendStatus(204);
});

这段代码设置了JSON解析、Cookie解析和CSRF保护。之后所有POST、PUT、DELETE请求都需要提供CSRF令牌。


登录接口:生成访问令牌和刷新令牌

import jwt from 'jsonwebtoken';

const isProd = process.env.NODE_ENV === 'production';

app.post('/api/token', (req, res) => {
  const { username, password } = req.body;

  // 这里应该验证用户名和密码
  if (!isValidUser(username, password)) {
    return res.status(401).json({ error: '用户名或密码错误' });
  }

  const accessToken = jwt.sign({ sub: username }, process.env.ACCESS_SECRET, { 
    expiresIn: '15m' 
  });
  
  const refreshToken = jwt.sign({ sub: username }, process.env.REFRESH_SECRET, { 
    expiresIn: '7d' 
  });

  res
    .cookie('access_token', accessToken, {
      httpOnly: true,
      sameSite: 'strict',
      secure: isProd,
      maxAge: 15 * 60 * 1000
    })
    .cookie('refresh_token', refreshToken, {
      httpOnly: true,
      sameSite: 'strict',
      secure: isProd,
      maxAge: 7 * 24 * 60 * 60 * 1000
    })
    .json({ user: username, status: 'active' });
});

验证通过后生成两个令牌,都设置为HttpOnly Cookie,JavaScript无法读取,但浏览器会自动发送。


刷新令牌接口

app.post('/api/token/refresh', (req, res) => {
  const refreshToken = req.cookies.refresh_token;
  if (!refreshToken) return res.sendStatus(401);

  jwt.verify(refreshToken, process.env.REFRESH_SECRET, (err, decoded) => {
    if (err) return res.sendStatus(403);

    const newAccess = jwt.sign({ sub: decoded.sub }, process.env.ACCESS_SECRET, { 
      expiresIn: '15m' 
    });
    
    const newRefresh = jwt.sign({ sub: decoded.sub }, process.env.REFRESH_SECRET, { 
      expiresIn: '7d' 
    });

    res
      .cookie('access_token', newAccess, {
        httpOnly: true,
        sameSite: 'strict',
        secure: isProd,
        maxAge: 15 * 60 * 1000
      })
      .cookie('refresh_token', newRefresh, {
        httpOnly: true,
        sameSite: 'strict',
        secure: isProd,
        maxAge: 7 * 24 * 60 * 60 * 1000
      })
      .json({ ok: true });
  });
});

这个接口用刷新令牌来获取新的访问令牌,保持用户登录状态。


前端react应用的使用方法

获取CSRF令牌:

async function getCSRF() {
  const res = await fetch('/api/csrf-token', { 
    credentials: 'include' 
  });
  return res.headers.get('x-csrf-token');
}

登录函数

const loginUser = async (formData) => {
  const csrf = await getCSRF();

  const res = await fetch('/api/token', {
    method: 'POST',
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json',
      'X-CSRF-Token': csrf
    },
    body: JSON.stringify(formData)
  });

  if (res.ok) {
    const data = await res.json();
    // 只存储非敏感信息
    localStorage.setItem('username', data.user);
  } else {
    throw new Error('登录失败');
  }
};

自动刷新令牌:

useEffect(() => {
  const refreshInterval = setInterval(async () => {
    try {
      const csrf = await getCSRF();
      await fetch('/api/token/refresh', {
        method: 'POST',
        credentials: 'include',
        headers: { 
          'Content-Type': 'application/json', 
          'X-CSRF-Token': csrf 
        }
      });
    } catch (error) {
      console.log('令牌刷新失败');
    }
  }, 14 * 60 * 1000); // 每14分钟刷新一次

  return () => clearInterval(refreshInterval);
}, []);


实际应用场景

  1. 文件上传

const uploadFile = async (file) => {
  const formData = new FormData();
  formData.append('file', file);

  await fetch('/api/upload', {
    method: 'POST',
    body: formData,
    credentials: 'include'
  });
};

  1. Next.js服务端渲染

export async function getServerSideProps({ req }) {
  const token = req.cookies.access_token;
  // 用令牌获取用户数据
  return { props: { userData } };
}

  1. WebSocket连接

const socket = new WebSocket(
  `wss://example.com/chat?token=${getTokenFromCookie()}`
);

  1. 移动端React Native

// 使用Keychain或SecureStore存储令牌
import Keychain from 'react-native-keychain';

const storeToken = async (token) => {
  await Keychain.setGenericPassword('token', token);
};


安全建议

  1. 设置合适的令牌过期时间

  2. 生产环境一定要启用Secure标志

  3. 使用SameSite防止CSRF攻击

  4. 定期轮换密钥

  5. 监控异常登录行为


总结

从localStorage切换到HttpOnly Cookie管理JWT令牌,虽然需要一些额外配置,但能显著提高应用安全性。这种方法适合大多数Web应用,特别是需要较高安全要求的项目。

现代浏览器都支持这些特性,现在就开始升级你的认证系统吧。记住,安全是一个持续的过程,需要定期审查和更新安全措施。


本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!

链接: https://fly63.com/article/detial/12907

angular + nodejs + CORS 实现跨域不丢失cookie做法

angular的4200端口 向 nodejs的8080端口 发送一个post请求, 这会造成跨域问题,跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制

js实现cookie有效期至当次日凌晨

实际开发中有要求用户一些行为每天一次,次日开始重新回复功能,一般前端都是通过cookie来记住用户的操作,然后进行判断当日是否还有机会,这时候需要给存储的cookie值一个有效期,让次日自动失效,重新计数,代码如下:

你真的了解 Cookie 和 Session 吗?

我在做面试官的时候,曾经问过很多朋友这个问题: Cookie 和 Session 有什么区别呢?大部分的面试者应该都可以说上一两句,比如:什么是 Cookie?什么是 Session?两者的区别等。

关于cookie domain中的点前缀

浏览器已经接收指令,之前在一级域名下存储了相关的信息。这里为了简化问题,假设我们有两个应用A和B,域名分别为:a.b.com和c.a.b.com。(显然B是A的一个子域)。

jquery之cookie操作_jquery.cookie.js

定义:让网站服务器把少量数据储存到客户端的硬盘或内存,从客户端的硬盘读取数据的一种技术;下载与引入:jquery.cookie.js基于jquery;先引入jquery,再引入:jquery.cookie.js

JS中设置cookie,读取cookie,删除cookie

Cookie 是在您的计算机上存储在小的文本文件中的数据。当 web 服务器向浏览器发送网页后,连接被关闭,服务器会忘记用户的一切。Cookie 是为了解决“如何记住用户信息”而发明的:若要想修改某个cookie,只需要用此种方法

使用vue-cookies处理cookie

创建:this.$配置时候设置的名称.set(\\\'cookies的key\\\',value),获取指定的key:this.$配置时候设置的名称.get(\\\'cookies的key`),获取所有keys返回为数组的形式:this.$配置时候设置的名称.keys (\\\'cookies的key`)

使用js实现对cookie的增删改查

存储cookie(key为test;value为testValue);而在cookie的名中我们可以控制,但要保存的值是不确定的,怎么办呢?这时就需要用escape()函数进行编码,它能将一些特殊符号使用十六进制表示

Js操作Cookie

Web 服务器和 HTTP 服务器是无状态的,因此当 Web 服务器将网页发送到浏览器时,连接会被断开,服务器会忘记与用户相关的所有内容。那么浏览器和 Web 服务器是怎样记住用户信息的?cookie 被发明出来解决这个问题。

Js cookie工具函数封装

CookieUtil.get() 方法根据 cookie 的名字获取相应的值。它会在 document.cookie 字符串中查找 cookie 名加上等于号的位置。如果找到了,那么使用 indexOf() 查找该位置之后的第一个分号(表示了该 cookie 的结束位置)。如果没有找到分号

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!