angularjs 如何将JWT令牌存储在HTTP唯一的cookie中?

jv4diomz  于 7个月前  发布在  Angular
关注(0)|答案(2)|浏览(78)

我已经创建了一个应用程序,它只使用服务器在正确的登录凭证后发送的JWT,并对我的后端服务器上的任何/api路由进行授权。
另一方面,AngularJS获取这个令牌,将其存储在会话存储中,并在每次运行时使用auth拦截器将令牌发送回服务器。
我最近才明白这种做法有多危险。
我理解这种情况下令牌来回传输的方法,但是,有人能解释一下,在高层次上,当你想把JWT存储在一个安全的,客户端JavaScript无法读取的HTTP唯一cookie中时,会发生什么?
例如:凭证成功后
1.在服务器上创建cookie,
1.在创建cookie的同时创建JWT
1.将JWT存储在名为token等的cookie属性中。
如果我的理解是正确的,这样做就不再需要auth拦截器了,因为在正确的凭据登录后,服务器将完成cookie中令牌的所有传输。

t1rydlwq

t1rydlwq1#

处理cookies有其相当的微妙之处,但在高层次上,cookie是一个数据,您的网络服务器可以设置,然后将由用户的网络浏览器存储,并发送回服务器的任何未来的请求,浏览器作出的相同的服务器,只要cookie是有效的,适用于正在作出的请求。
(* 这就是为什么您不再需要使用Angular拦截器的原因,因为它是浏览器本身确保cookie被发送
除了一些特殊的旗标选项(例如仅限HTTP)之外,您还可以在较高层级设定Cookie,让它与指定的网域和路径相关联。例如,您的服务器可以设定Cookie,让它只会在稍后由浏览器传送给/api路径下的要求。
总而言之,cookie是HTTP的一种状态管理机制,有关更多详细信息,请参见相关的RFC 2617
与之相反,JWT只是一些具有众所周知的表示形式并遵循一些约定的数据。更具体地说,JWT由头部、有效负载和签名部分组成,对于大多数JWT用例,通常建议保持有效负载的大小较小。有关更多详细信息,请参见Get Started with JSON Web Tokens
如果您阅读上一篇文章,您会注意到JWT的最终表示形式是三个用点分隔的Base64 url编码字符串。这一点特别有趣,因为它意味着JWT非常适合在HTTP中使用,包括作为cookie的值。
需要记住的一点是,根据规范,您只能保证浏览器将支持每个cookie最多4096字节的cookie(由cookie的名称、值和属性的长度之和来衡量)。除非你在令牌中存储了太多的数据,否则你不应该有问题,但这总是要考虑的。是的,您还可以将一个JWT标记分解为多个cookie,但是事情开始变得更加复杂。
此外,Cookie有其过期的概念,所以也要记住这一点,因为在身份验证范围内使用JWT时,JWT本身也有其自己的过期概念。
最后,我只想解决您对在localStorage/sessionStorage中存储JWT的一些担忧。如果您这样做,您必须理解它的含义,这是正确的,例如,与存储关联的域中的任何JavaScript代码都将能够读取该令牌。然而,仅限HTTP的cookie也不是一个灵丹妙药。我将给予下面的文章:Cookies vs Tokens: The Definitive Guide的函数。
它重点介绍了传统会话标识符Cookie与基于令牌(JWT)的身份验证系统之间的区别,名为 * 在哪里存储令牌?
的部分保证读者能够阅读,因为它处理了存储的安全性相关方面。
面向TL:DR人员的摘要:
跨站点脚本(XSS)和跨站点请求伪造(XSRF或CSRF)是网站面临的两种最常见的攻击媒介。当外部实体能够在您的网站或应用程序中执行代码时,就会发生跨站点脚本(XSS)攻击。
如果攻击者可以在您的域上执行代码,则您的JWT令牌(* 在本地存储 * 中)是易受攻击的。
如果您使用的是带有本地存储的JWT,那么跨站点请求伪造攻击就不会成为问题。另一方面,如果您的用例要求您将JWT存储在cookie中,那么您需要防范XSRF。
(重点是我)

ttp71kqs

ttp71kqs2#

基本上,我保存access_token(jwt)在用户登录时存储在数据库中的刷新令牌对象中。

const newToken = new RefreshToken({
        issuedUtc: moment().unix(), /* Current unix date & time */
        expiresUtc: moment().add(4, "days").unix(), /* Current unix date&time + 4 days */
        token: refreshToken, /* Generate random token */
        user: data.id, /* user id */
        /* Signing the access Token */
        access_token: jwt.sign(
          { sub: data.id, user: userWithoutHash },
          Config.secret,
          {
            issuer: "http://localhost:3000",
            expiresIn: "30m", // Expires in 30 minutes
          }
        ),
});

字符串
生成并保存的兰德令牌然后作为httpOnly cookie发送到浏览器;

res.cookie("refreshToken", newToken.token, {
          httpOnly: true,
          sameSite: "strict",
});


由于浏览器为每个请求发送cookie,剩下的就是在受保护的路由上使用中间件,从cookie中检索令牌,通过在数据库中查找令牌来验证它是否存在,检查它是否尚未过期,尝试验证数据库中保存的用于刷新令牌的访问令牌,如果它过期,则签署新的jwt并更新数据库中的刷新令牌,然后允许用户继续到受保护的路由,如果它有效,则简单地允许用户继续到受保护的路由。如果刷新令牌已经过期,将用户重定向到登录页面,最后,如果没有收到刷新令牌,也将用户重定向到登录页面。

var cookie = await getcookie(req); // get the cookie as js object using my custom helper function

/* Check if refresh token was received */

if (cookie.refreshToken) {

  /* Check find the refresh token object in the database */

  var refreshToken = await RefreshToken.findOne({
    token: cookie.refreshToken,
  });

  /* Check if the refresh token is still valid using expiry date */

  if (moment.unix(refreshToken.expiresIn) > moment.now()) {

    /* If the condition is fulfilled try to verify the access token using jwt */

    jwt.verify(refreshToken.access_token, Config.secret, async (err, result) => {

      /* in callback check for error */

      if (err) {

        /* If error this means the access_token is expired, so find and update the user's refresh token with a newly signed access token */

        await RefreshToken.findByIdAndUpdate(refreshToken.id, {
          access_token: jwt.sign(
            { sub: result.id, user: result.user },
            Config.secret,
            {
              issuer: "http://localhost:3000",
              expiresIn: "30m", // Expires in 30 minutes
            }
          ),
        });

        /* Proceed to save the user in a local variable then call next */

        res.locals.user = result.user;
        return next();
      }

      /* If no error proceed by saving the user in a local variable then call next */

      res.locals.user = result.user;
      return next();
    });

  } else {

    /* If the refresh token is expired, then redirect to log in */

    return res.status(401).redirect('/login');
  }
} else {

  /* If no refresh token is provided, then redirect to log in */

  return res.status(401).redirect('/login');
}


这是我自己想出来的,所以我不能说这是充分的证明,但由于httpOnly cookie不能在DOM中访问,在DOM中运行恶意脚本不能访问刷新令牌,即使刷新令牌以某种方式福尔斯落入坏人手中,那么它也将是无用的,因为它在到达服务器之前根本不保存任何信息。使用刷新令牌泄漏任何信息是非常不可能的。

相关问题