JWT 验证客户端?
Posted
技术标签:
【中文标题】JWT 验证客户端?【英文标题】:JWT Verify client-side? 【发布时间】:2017-10-02 22:37:56 【问题描述】:我有一个带有角度前端的 nodejs api。 API 成功地使用带有护照的 JWT 来保护它的端点。
我现在意识到,在令牌过期后,我的前端仍将允许用户请求我的 api 端点,而不会提示他们重新输入登录详细信息以获取新令牌。
这是我的后端生成令牌的方式:
function generateToken(user)
return jwt.sign(user, secret,
expiresIn: 10080 // in seconds
);
所以要实现这个逻辑,我想我需要在客户端验证 JWT 令牌。 Q1,这是一个明智的做法。
Q2,我正在使用的JWT
库似乎需要一个公钥才能使用它的verify()
函数。我似乎没有公钥,只有一个秘密,我只是编造的,所以它不是用一对生成的。我的公钥来自哪里,或者没有这个验证我的令牌的另一种方法?
这一切似乎应该是显而易见的,我错过了一些东西,如果这是一个愚蠢的问题,请道歉,但我似乎找不到答案?
【问题讨论】:
【参考方案1】:答案 1:它不被认为是在客户端验证您的身份验证令牌的好方法,因为它在编码/解码时涉及密钥,并且 保留密钥客户端不安全。
创建令牌
jwt.sign(
data: 'foobar'
, 'secret', expiresIn: 60 * 60 );
验证令牌
jwt.verify(token, 'secret', function(err, decoded)
console.log(decoded.foo) // bar
);
答案 2:JWT 在编码和解码令牌时涉及秘密OR公钥。它必须在服务器端的某个地方声明或保存在配置文件中。
说明: 解码意味着从 Base64 解码,该过程不涉及密钥。另一方面,验证 JWT 需要密钥,因为它会涉及加密签名操作。
总而言之,解码不需要秘密(记住解码只是解释 base64),验证/签名确实需要它
【讨论】:
签名使用公钥(例如RS256)【参考方案2】:我认为在客户端验证 JWT 令牌不是一个好主意。 海事组织;
每当用户登录时,生成访问和刷新令牌并返回给用户类似这样的内容;
"accessToken": <<accessToken>>
"refreshToken": <<refreshToken>>
"expiresAt": <<expiresAt>>
因此客户端可以了解访问令牌何时过期并可以使用刷新令牌对其进行刷新。
加密您放入访问令牌中的数据,因为有机会在没有密钥的情况下访问数据。但是当然有人需要密钥来验证。
【讨论】:
【参考方案3】:Q1:在客户端进行令牌验证是个坏主意。您可以做的是将令牌与相同的过期日期一起保存在客户端上,然后刷新/删除令牌。但我认为最好在服务器端进行一些日期检查,因为存在简单的规则:不要信任客户端,因为它总是可以发送恶意代码。
Q2: JWT 不需要任何公钥。它总是必须在服务器端存储私钥,因为如果有人知道你的密钥,你的令牌没有任何意义。您只能添加一些有效负载以使其更复杂。
【讨论】:
【参考方案4】:TL;DR
-
您必须验证服务器中的 JWS 签名始终。
客户端签名验证并没有提供太多帮助,除非您有特定情况认为它是有意义的不要这样做。
您无需验证 JWS 令牌的签名即可在客户端中检查过期。 (除非您使用 JWE 对声明进行加密,在这种情况下,您需要做类似的事情,因为您需要密钥来解密声明)。
您不需要验证 JWS 的签名来检查服务器中的过期时间,但您应该这样做,因为这样可以确定没有人更改过期时间(否则验证将失败,因为如果声明发生更改,那么重新计算的签名会有所不同)
要读取未加密的声明,您只需对其进行解码。您可以在客户端中使用jwt-decode。
我现在意识到,在令牌过期后,我的前端仍将允许用户请求我的 api 端点 [...]
所以要实现这个逻辑,我想我需要在客户端验证 JWT 令牌
如果我对您的理解正确,您说的是在客户端检查 JWS 是否已过期。
为此,您无需验证令牌签名(尽管您使用的库似乎正在为您执行both things at the same time,但也允许您使用ignoreExpiration
标志禁用过期控制)。 (除非您正在加密声明,也就是使用 JWE)
RFC 7515 (JWS) 没有说明过期。 Message Signature or MAC Validation 不控制过期(它不应该因为签名为您提供真实性和完整性)。
如果 JWT is valid or not,即使 RFC 7519 (JWT) 也无法控制到期声明以进行解析。
还有,所有的claims are optional。
因此,您可以在不验证签名的情况下检查 JWT 是否已过期,因此您不需要公钥(用于 RSA 等非对称加密)或密钥(用于 AES 等对称加密)。 在 JWT 和 JWS 令牌中,声明只是纯文本 base64 编码,因此您只需 decode the payload without verifying if the signature is valid 并阅读到期声明。 如果您正在加密有效负载(也就是使用 JWE),那么您将无法执行此操作。
来自jjwt library的备注
JWT 可以加密签名(使其成为JWS)或加密(使其成为JWE) .
Here 是一个来自 auth0 的 ligthweigth 库,用于解码 JWT/JWS 令牌的 base64 编码声明。 一个人甚至在询问checking expiration。
我不知道你为什么认为你应该在客户端做这个控制,唯一的好处是避免发送客户端知道会失败的 API 请求。他们应该会失败,因为服务器应该验证令牌没有过期,显然之前的签名验证(使用密钥/私钥)。
RFC 7519 谈到了这一说法:
“exp”(过期时间)声明标识过期时间 或之后,JWT 不得接受处理。
在您所说的 Web 应用程序中,令牌的使用是允许无状态服务器对客户端请求进行身份验证。 OPTIONAL 过期声明的目标是允许服务器对生成的 JWS 有一些控制(如果我们使用 JWT 进行身份验证,对它们进行签名是必须的,所以我们应该谈论 JWS)。
如果没有过期,令牌将永远有效,或者直到用于签名它们的密钥发生变化(这将使验证过程失败)。 顺便说一句,invalidatingsessions 是使用无状态身份验证最臭名昭著的缺点之一。
如果我们在用于授权的 JWS 有效负载(又名声明)中包含信息,例如用户拥有哪些角色,会话失效将成为一个真正的问题。
来自Stop using JWT for sessions
但更严重的是,这也可能意味着某人拥有具有管理员角色的令牌,即使您刚刚撤销了他们的管理员角色。因为您也不能使令牌无效,所以您无法删除其管理员访问权限
过期控制并没有解决这个问题,我认为更倾向于避免会话劫持或 CSRF 攻击。
使用 CSRF 的攻击者将能够向您的 API 发出带有过期 JWS 的请求,从而跳过过期控制。
另一个问题是使用公钥或私钥验证客户端中的签名。
关于你的问题
我使用的似乎需要一个公钥才能使用它的 verify() 函数。我好像没有公钥,只有一个秘密,是我自己编的,所以不是用pair生成的。
您指出的验证方法明确表示它接受公钥或密钥。
jwt.verify(token, secretOrPublicKey, [options, callback])
secretOrPublicKey 是一个字符串或缓冲区,其中包含 HMAC 算法的密钥或 RSA 和 ECDSA 的 PEM 编码公钥
我假设您两者都没有使用,而您使用的是像 'shhhh' 这样的字符串。
var token = jwt.sign( data: '¿Donde esta Santiago?', 'shhhh');
那你应该这样做
var decoded = jwt.verify(token, 'shhhhh');
然而,这里的问题是:客户端签名验证真的需要吗?
我认为不是,至少对于这种客户端仅使用 JWS 向服务器发送后续请求的应用程序来说:“嘿服务器,我是 Gabriel,我在这里有一篇论文(令牌)向您保证,并且该文件已由您签名。” 因此,如果客户端不验证 JWS 并且 MITM 已成功向该客户端提供了一个由他自己签名的 JWS(而不是由服务器签名的 JWS),那么后续请求将简单地失败。 和过期控制一样,签名验证只是防止客户端发出会失败的请求。
现在,客户端验证需要发送公钥或私钥。 发送公钥并不代表安全问题,但它是额外的努力和处理,几乎没有什么好处。
发送密钥(如“shhhh”)可能表示安全问题,因为用于签署令牌的密钥相同。
【讨论】:
如果我理解得很好,jwt.verify 会在数据库中检索用户,它是什么? 来自docs:如果签名有效并且可选的过期、受众或颁发者有效,则返回已解码的有效负载。如果没有,它会抛出错误。你可以在payload上放任何你想要的东西。 在使用异步 RS256 时,您确实需要在客户端验证 jwt 令牌,这是推荐的方式。原因是服务器本身不可能是你的服务器——反向代理——或者“伪装成你的服务器的服务器”可能会发布令牌并猜猜是什么?此时,您的客户端可能正在向假装服务器发送带有信息的有效负载。这是不好的。所以你会怎么做?在将其发送到服务器之前,您在客户端上验证 jwt。所以你是对的,服务器在收到无效令牌时应该返回 401 或其他东西 嗨,在我看来,当使用非对称算法时,在客户端或至少在服务器端进行一些验证但不调用身份验证服务器是完全可以的(例如:Next.js充当代理的中间件,您可能希望在那里检查令牌)。在这种情况下,有一个公钥,没有理由将其保密。但是,我很难找到可靠的信息,Stack Overflow 的答案相互矛盾,并且没有提供足够的来源。 受众价值检查谁?浏览器还是服务器?【参考方案5】:把它放在这里是为了那些在浏览器上使用公钥寻找 jwt 验证的人。
图书馆: https://kjur.github.io/jsrsasign/
示例: https://kjur.github.io/jsrsasign/tool/tool_jwtveri.html
代码示例: https://github.com/kjur/jsrsasign/blob/master/tool/tool_jwtveri.html
API: https://kjur.github.io/jsrsasign/api/symbols/KJUR.jws.JWS.html#.verifyJWT
附:不要使用您的密钥进行浏览器 jwt 验证!仅限公钥!
【讨论】:
【参考方案6】:在客户端管理令牌何时过期,这样您就可以避免发送您知道会被拒绝的令牌,这纯粹是一种优化,可以避免与服务器的额外往返。这是一个完全有效的问题,但不是安全问题。由您决定是否需要该级别的优化。出于安全考虑,服务器必须验证令牌签名并拒绝过期令牌。令牌不需要加密,除非它们包含您不希望最终用户或以某种方式获得令牌副本的攻击者可见的敏感数据。作为良好的安全实践,应通过 HTTPS / SSL 传输令牌。访问令牌通常是短暂的。如果您还使用刷新令牌,切勿将其存储在浏览器中,除非它是服务器为同一源域设置的安全 cookie,并且浏览器脚本无法访问。在这种情况下,刷新令牌仍应定期轮换。
【讨论】:
以上是关于JWT 验证客户端?的主要内容,如果未能解决你的问题,请参考以下文章