返回

NestJS授权:令牌 vs. HttpOnly Cookies,如何选?

javascript

NestJS 授权方案:令牌 vs. HttpOnly Cookies

在构建安全 API 时,用户身份验证和授权至关重要。NestJS 提供了一个清晰的框架,支持使用令牌或 HttpOnly Cookies 进行授权,但这两种方法都有各自的优缺点。选择正确的方案需要仔细衡量各种因素,包括安全,可用性和兼容性。

基于令牌的授权

在 NestJS 官方文档中,常见的做法是在用户登录后返回一个访问令牌 (access token)。这个令牌,通常是 JSON Web Token (JWT),会在每次 API 请求时包含在请求头中(通常是 Authorization: Bearer <token>)。

优点:

  • 无状态: 服务器不需要存储会话信息,令牌本身就包含了足够的用户信息。这种方式可以更容易地进行水平扩展。
  • 跨域: 可以很方便地应用于移动应用或 SPA (单页面应用)等场景。
  • 易于实施: 大多数客户端库和浏览器都支持通过 Authorization header 发送令牌。

缺点:

  • XSS风险: 令牌通常存储在客户端(如 localStorage,sessionStorage,或者内存),易受跨站脚本攻击 (XSS)。攻击者如果能够执行 JS 代码,便能获取到令牌,进而模拟用户。
  • 令牌盗用: 客户端的令牌可能在传输或存储的过程中被盗用。

代码示例: (根据示例代码已给出,此处仅简述关键点)
登录时返回访问令牌:

// AuthService
async signIn(username: string, pass: string): Promise<{ access_token: string }> {
  // ...验证用户
  const payload = { sub: user.userId, username: user.username };
  return {
    access_token: await this.jwtService.signAsync(payload),
  };
}

前端需要在后续请求中通过 Authorization 请求头传递令牌, 示例如下:

// 使用 JavaScript 发起请求,需要设置 Authorization header
fetch('/api/profile', {
    headers: {
       'Authorization': `Bearer ${accessToken}`
     }
   });

基于 HttpOnly Cookies 的授权

另一种方法是使用 HttpOnly cookies。在用户登录后,服务器生成并发送带有令牌的 cookie 到客户端。该 cookie 会被浏览器自动在后续请求中附加,并且 HttpOnly 标志会阻止 JavaScript 读取该 cookie 内容,减少了 XSS 攻击风险。

优点:

  • 安全性更高: HttpOnly cookie 可以有效防止 XSS 攻击。由于 JavaScript 不能直接访问,令牌更不容易泄露。
  • CSRF防护: 使用 Cookie 时通常需要同时考虑跨站请求伪造(CSRF),但这也是一种保护机制。

缺点:

  • 限制性强: HttpOnly cookie 与浏览器同源策略关联,对跨域请求会增加配置上的复杂度,例如需进行 CORS 协商,在涉及到移动 App 时会存在不兼容等问题。
  • 非无状态: 由于Cookie需要客户端自动附带发送,在服务端可能会根据业务逻辑需要记录会话信息,可能使得API服务器从本质上变为有状态。
  • 不易处理刷新令牌: 相对于直接存储在前端更容易在不同端做操作,例如刷新过期时间的操作会稍微繁琐。

代码示例:
登录时设置 HttpOnly cookie:

// Auth Controller
@Post('login')
@HttpCode(HttpStatus.OK)
async signIn(@Body() signInDto: Record<string, any>, @Res({ passthrough: true }) response: Response) {
    const { access_token } = await this.authService.signIn(signInDto.username, signInDto.password);
      response.cookie('auth_token', access_token, {
      httpOnly: true,
      secure: true,  //建议在HTTPS环境中使用
      sameSite: 'strict', //  推荐使用 strict 防止CSRF攻击, 或 lax 根据业务进行选择
    });
    return { message: 'login success' }; //不直接返回token
}

// 在 authGuard 中从cookie中读取token
private extractTokenFromCookie(request: Request): string | undefined {
    return request.cookies?.auth_token;
}

需要在 Guard 中修改 extractTokenFromHeader 方法,改成从 cookie 中读取令牌的方法。 同时需要注意的是,需要安装 cookie-parser 中间件:

npm i cookie-parser

main.ts中注册:

// main.ts
import * as cookieParser from 'cookie-parser';
app.use(cookieParser());

应该如何选择

根据你的应用场景和安全需求选择方案:

  • 如果应用是单页面应用、移动应用或者跨多个域的应用, 采用基于令牌的方式。 请考虑使用安全存储或中间件技术来防范 XSS 攻击。
  • 如果你的应用程序是前后端都在同一域名下,更推荐使用HttpOnly cookies方案来加强安全性。 使用合适的 SameSite 和 Secure 属性。

需要权衡安全性和实施复杂性。 选择方案需满足安全性,可用性,开发成本等要求。对于高安全敏感应用,应该始终考虑使用 HttpOnly cookies,但这意味着你需要在应用程序设计上作出调整以适应同源策略带来的限制。 如果你对安全要求不是极高,只是普通的应用或者原型开发,可以采用客户端存储token的方案,实现快速的开发和部署。

总的来说,没有一个万能的解决方案,根据你的具体需求,进行权衡取舍才能得到更合适方案。 务必确保遵循最佳的安全实践,对授权方案进行细致的测试和审查。