我创建了一个应用程序,它只使用服务器在正确登录凭据时发送的JWT,并对我的后端Express.js服务器上的任何/api
路由进行授权。
我最近才明白这种做法有多危险。
在这种情况下,我理解来回传递令牌的方法。但是,如果您希望将JWT存储在客户端Javascript无法读取的安全,仅HTTP的cookie中,那么有人会非常友好地解释发生的方法吗?
例如:凭证成功
我正试图在这里获得一个关于它是如何工作的心智模型。如果我的理解是正确的,那么这样做就不再需要auth拦截器了,因为在正确的凭证登录时,服务器会在cookie中进行所有的令牌转移。
答案 0 :(得分:8)
处理cookie具有公平的微妙之处,但在较高的层面上,cookie是您的Web服务器可以设置的一段数据,然后由用户的Web浏览器存储并发送回任何服务器。只要cookie有效且适用于正在进行的请求,浏览器对同一服务器发出的未来请求。
(这就是为什么你不再需要使用Angular拦截器,因为浏览器本身可以确保发送cookie )
除了一些特殊标志选项,例如仅HTTP,在更高级别,您可以将cookie设置为与给定域和路径相关联。例如,您的服务器可以设置Cookie,以便以后只能由浏览器发送到/api
路径下发出的请求。
总结一下,cookie是HTTP的状态管理机制,有关更多详细信息,请参阅相关的RFC 2617。
相比之下,JWT只是一些具有众所周知的表示并遵循一些约定的数据。更具体地说,JWT由头部,有效载荷和签名部分组成,并且通常建议在大多数JWT用例中保持有效载荷的大小。有关详细信息,请参阅Get Started with JSON Web Tokens。
如果您浏览上一篇文章,您会注意到JWT的最终表示形式是由点分隔的三个Base64url编码字符串。这特别令人感兴趣,因为它意味着JWT非常适合在HTTP中使用,包括作为cookie的值。
要记住的一点是,通过规范,您只能保证浏览器将支持每个cookie最多4096个字节的cookie(以cookie的名称,值和属性的长度总和来衡量) 。除非您存储令牌中的大量数据,否则您不应该遇到问题,但始终需要考虑。是的,您也可以将JWT令牌分解为多个cookie,但事情开始变得更加复杂。
此外,Cookie有他们的到期概念,所以请记住,因为JWT本身在身份验证范围内使用时也会有自己的到期概念。
最后,我想解决一些关于在localStorage
/ sessionStorage
中存储JWT的问题。你是对的,如果你这样做,你必须理解它的含义,例如,与存储相关联的域中的任何Javascript代码都能够读取令牌。但是,仅HTTP的cookie也不是银弹。我会给以下文章写一下:Cookies vs Tokens: The Definitive Guide。
它侧重于传统会话标识符cookie与基于令牌的(JWT)身份验证系统之间的差异,名为存储令牌的位置?的部分保证读取,因为它解决了与安全性相关的问题存储。
TL的摘要:DR人员:
面向网站的两种最常见的攻击媒介是跨站点 脚本(XSS)和跨站点请求伪造(XSRF或CSRF)。当外部实体能够在您的网站或应用程序中执行代码时,就会发生跨站点脚本攻击。 (...)
如果攻击者可以在您的域上执行代码,则您的JWT令牌(本地存储中的 )容易受到攻击。 (...)
如果您将JWT与本地存储一起使用,则跨站点请求伪造攻击不是问题。另一方面,如果您的用例要求您将JWT存储在cookie中,则需要防范XSRF。
(重点是我的)
答案 1 :(得分:0)
是的,它不需要任何身份验证拦截器。浏览器会将Cookie发送到为每个请求设置的相同来源。
答案 2 :(得分:0)
基本上,当用户登录时,我将 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
}
),
});
生成并保存的 rand 令牌然后作为 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');
}
这是我自己想出来的,所以我不能说它是完整的,但是由于在 DOM 中无法访问 httpOnly cookie,在 DOM 中运行恶意脚本无法访问刷新令牌,即使刷新令牌不知何故落入坏人之手,那么它将毫无用处,因为它在到达服务器之前根本不保存任何信息。因此,只要在服务器上设置了正确的 cors 标头,就极不可能使用刷新令牌泄露任何信息。