You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

将 accessToken 和 refreshToken 存储在 cookie 中的最佳方法

针对你在 .NET 9 后端 + Angular 20 前端技术栈中,基于 Cookie 的身份认证安全优化问题,下面分点解答你的疑问并给出行业推荐的实践方案:

不推荐合并存储,核心原因如下:

  • 安全属性配置冲突
    refreshToken 必须配置为 HttpOnly(禁止前端 JavaScript 读取,避免XSS攻击窃取),而如果你的 accessToken 需要前端读取并在请求头中携带(比如 Authorization: Bearer {token}),则不能设置 HttpOnly。合并后无法为两个令牌分别配置关键安全属性,会直接降低安全性。
  • 有效期与生命周期差异
    accessToken 通常有效期较短(15-30分钟),refreshToken 有效期较长(7-30天)。分离存储可以独立处理刷新逻辑(比如仅刷新 accessToken 时,不需要更新 refreshToken 的 Cookie),合并后会导致不必要的 Cookie 重写操作。
  • Cookie 大小限制
    浏览器单 Cookie 大小上限为 4KB,若两个令牌(尤其是JWT)长度较长,合并后可能超出限制,导致 Cookie 被截断失效。
  • 风险扩大化
    若合并后的 Cookie 被泄露(比如CSRF攻击成功),攻击者会同时获取两个令牌,直接拥有长期的身份权限;分离存储则可以针对性撤销其中一个令牌的权限。

二、使用基于会话的方法(Cookie 中存储会话 ID)是否比直接存储令牌更好?

这两种模式各有优劣,需根据你的系统架构选择:

会话 ID 模式(状态化)

优势

  • 仅在 Cookie 中存储短长度的会话ID,敏感的用户身份数据全部存储在后端(比如Redis、数据库),即使 Cookie 被窃取,攻击者需要额外的会话劫持手段才能获取权限,且后端可以主动失效会话。
  • 无需前端处理令牌刷新、过期逻辑,身份状态由后端统一管理,降低前端复杂度。
    劣势
  • 状态化架构,分布式部署时需要共享会话存储(比如Redis集群),增加运维复杂度。
  • 不适合无状态的微服务架构,扩展性受限。

直接存储令牌模式(无状态,如JWT)

优势

  • 无状态设计,后端无需存储会话数据,适合分布式、微服务架构,扩展性强。
  • 令牌自身包含身份信息,验证时无需查询数据库,性能更高。
    劣势
  • 令牌一旦签发无法主动撤销(除非维护令牌黑名单),若令牌泄露,攻击者可使用至过期。
  • 需要前端处理令牌刷新、过期重定向等逻辑,增加前端开发复杂度。

选择建议
如果你的系统是单体应用或小规模集群,会话ID模式更简单安全;如果是分布式微服务架构,推荐无状态的令牌模式。

三、.NET + Angular 技术栈中,Cookie 身份认证的推荐安全模式

结合前后端分离架构的特性,推荐以下分层安全实践:

1. 令牌存储策略(核心)

(1)RefreshToken 存储

  • 强制配置:存入独立的 Cookie,设置以下关键属性:
    • HttpOnly: true:禁止前端JS读取,彻底避免XSS窃取风险
    • Secure: true:仅在HTTPS环境下传输,防止明文泄露
    • SameSite: Strict:严格限制Cookie跨站发送,防范CSRF攻击
    • ExpireTimeSpan:设置为7-30天的长有效期
  • .NET 9 配置示例:
    builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie("RefreshTokenCookie", options =>
        {
            options.Cookie.HttpOnly = true;
            options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
            options.Cookie.SameSite = SameSiteMode.Strict;
            options.Cookie.Name = "App.RefreshToken";
            options.ExpireTimeSpan = TimeSpan.FromDays(14);
            options.LoginPath = "/auth/login";
        });
    
  • 刷新机制:当accessToken过期时,前端调用后端刷新接口,后端验证refreshToken后,返回新的accessToken,并更新refreshToken(令牌旋转,旧refreshToken失效)。

(2)AccessToken 存储

根据前端是否需要读取令牌,分两种方案:

  • 方案A:前端无需读取(推荐)
    存入独立的 HttpOnly, Secure, SameSite=Strict Cookie,后端通过 CookieAuthentication 自动验证令牌,前端请求时只需携带Cookie(Angular中设置withCredentials: true)。

    • 优势:完全避免XSS窃取accessToken的风险
    • Angular 全局配置示例(自动携带Cookie):
      import { HttpInterceptor, HttpRequest, HttpHandler } from '@angular/common/http';
      import { Injectable } from '@angular/core';
      
      @Injectable()
      export class CredentialsInterceptor implements HttpInterceptor {
        intercept(req: HttpRequest<any>, next: HttpHandler) {
          return next.handle(req.clone({ withCredentials: true }));
        }
      }
      
      // 在AppModule的providers中注册拦截器
      providers: [
        { provide: HTTP_INTERCEPTORS, useClass: CredentialsInterceptor, multi: true }
      ]
      
  • 方案B:前端需要读取(比如自定义请求头)
    存入非HttpOnly的Cookie,但必须配合以下防护:

    • 启用内容安全策略(CSP),限制非法脚本执行
    • 启用XSS防护(浏览器内置的X-XSS-Protection,或.NET的XSS过滤)
    • 尽量缩短accessToken的有效期(15分钟以内)

    注意:此方案存在XSS窃取风险,仅在前端必须读取令牌时使用,优先选择方案A。

2. 额外安全加固措施

  • 启用CSRF防护
    由于使用Cookie认证,必须启用CSRF防护,防止跨站请求伪造:

    • .NET 9 配置:
      builder.Services.AddAntiforgery(options =>
      {
          options.Cookie.Name = "XSRF-TOKEN";
          options.Cookie.HttpOnly = false; // 允许Angular读取
          options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
          options.HeaderName = "X-XSRF-TOKEN";
      });
      
    • Angular 会自动读取XSRF-TOKEN Cookie,并在POST/PUT/DELETE等请求中携带X-XSRF-TOKEN头,无需额外编码。
  • 使用HTTPS强制跳转
    在.NET中配置强制HTTPS:

    app.UseHttpsRedirection();
    // 启用HSTS增强HTTPS安全性
    builder.Services.AddHsts(options =>
    {
        options.Preload = true;
        options.IncludeSubDomains = true;
        options.MaxAge = TimeSpan.FromDays(365);
    });
    
  • 令牌旋转与失效

    • refreshToken每次刷新时生成新的令牌,旧令牌立即失效,防止令牌复用。
    • 若使用JWT,可维护令牌黑名单(比如Redis存储失效的JWT ID),实现主动令牌撤销。
  • 分布式场景优化
    若采用令牌模式,推荐使用Reference Token(而非JWT):

    • 后端存储令牌的完整身份信息,Cookie中仅存储短的引用ID
    • 验证时后端查询存储系统获取令牌内容,可主动撤销令牌
    • .NET中可通过IdentityServer或OpenIddict实现Reference Token。

总结

针对你的技术栈,最安全且易维护的模式是:

  1. 分离存储accessToken与refreshToken到不同Cookie,分别配置对应安全属性
  2. refreshToken严格使用HttpOnly, Secure, SameSite=Strict,后端管理刷新与旋转
  3. accessToken优先使用HttpOnly Cookie,后端自动验证,配合Angular的withCredentials发送请求
  4. 启用CSRF防护、HTTPS、短有效期accessToken等加固措施
  5. 分布式系统优先选择无状态令牌模式(JWT或Reference Token),单体系统可选择会话ID模式。

火山引擎 最新活动