JWT 验证客户端?

JWT Verify client-side?

我有一个带有 angular 前端的 nodejs api。 API 成功使用 JWT 和护照来保护它的端点。

我现在意识到,在令牌过期后,我的前端仍将允许用户请求我的 api 端点,而不会提示他们重新输入登录详细信息以获取新令牌。

这是我的后端生成令牌的方式:

function generateToken(user) {
  return jwt.sign(user, secret, {
    expiresIn: 10080 // in seconds
  });
}

因此,要实现此逻辑,我认为我需要在客户端验证 JWT 令牌。 Q1,这是明智的做法吗?

Q2,我正在使用的 JWT 库似乎需要一个 public 键才能使用它的 verify() 功能。我似乎没有 public 密钥,只有一个秘密,我刚刚编造的,所以它不是用一对生成的。我的 public 密钥来自哪里,或者是否有另一种方法可以在没有它的情况下验证我的令牌?

这一切看起来应该是显而易见的,我错过了一些东西,如果这是一个愚蠢的问题,我深表歉意,但我似乎找不到答案?

答案 1:在客户端验证您的身份验证令牌不是一个好方法,因为它涉及密钥,而 encoding/decoding 它和 在客户端保存密钥是不安全的

创建令牌

jwt.sign({ data: 'foobar' }, 'secret', { expiresIn: 60 * 60 });

验证令牌

jwt.verify(token, 'secret', function(err, decoded) { console.log(decoded.foo) // bar });

答案2:JWT在编解码token时涉及到secretORPublickey。它必须在服务器端某处的配置文件中声明或保存。

Explanation: Decoding means decoding from Base64, there's no secret key involved in that process. On the other hand, verifying a JWT would require a secret key because it would involve a cryptographic signature operation.

To sum up, decoding does not need the secret (remember decoding is just interpreting base64) and verifying/signing does require it

我认为在客户端验证 JWT 令牌不是一个好主意。
国际海事组织;

  1. 每当用户登录时,生成访问和刷新令牌以及 return 给用户这样的东西;

    { "accessToken": <<accessToken>> "refreshToken": <<refreshToken>> "expiresAt": <<expiresAt>> }

    因此客户端可以了解访问令牌何时过期并可以使用刷新令牌刷新它。

  2. 加密您放入访问令牌中的数据,因为有机会在没有密钥的情况下访问数据。但是当然有人需要密钥来验证。

Q1: 客户端的令牌验证不是个好主意。您可以做的是在客户端上保存一个令牌以及相同的过期日期,然后 refresh/remove 一个令牌。但我认为最好在服务器端进行一些日期检查,因为存在简单的规则:不要信任客户端,因为它总是可以发送恶意代码。

Q2: JWT 不需要任何 public 密钥。它始终必须在服务器端存储私钥,因为如果有人知道您的私钥,您的令牌就没有任何意义。您只能添加一些有效负载来使其更复杂。

TL;DR

  1. 您必须始终验证 服务器中的 JWS 签名
  2. 客户端签名验证 不会提供太多,除非你有一个特定的情况,它是有意义的不要这样做.
  3. 不需要验证 JWS 令牌的签名来检查客户端中的过期。 (除非你正在加密声明,也就是使用 JWE,在那种情况下你需要做类似的事情,因为你需要一个密钥来解密声明)。
  4. 您不需要验证 JWS 的签名来检查服务器中的过期时间,但您应该这样做,因为这可以让您确定没有人更改过期时间(否则验证将失败,因为如果声明更改然后重新计算的签名将不同)
  5. 要阅读未加密的声明,您只需对其进行解码即可。您可以在客户端中使用 jwt-decode

I am now conscious that after the tokens have expired, my front end will still allow the user to request my api endpoints [...]

So to implement this logic I think I need to verify the JWT token client-side

如果我没理解错的话,你说的是检查客户端的 JWS 是否已过期。 为此,您不需要验证令牌签名(尽管您使用的库似乎正在为您做 both things at the same time,但也允许您使用 ignoreExpiration 标志禁用过期控制) . (除非你正在加密声明,也就是使用 JWE) RFC 7515 (JWS) says nothing about expiration. Message Signature or MAC Validation 不控制过期(它不应该因为签名为您提供真实性和完整性)。 甚至 RFC 7519 (JWT) doesn't control the expiration claim for resolve if a JWT is valid or not.

此外,所有 claims are optional

因此,您可以在不验证签名的情况下检查 JWT 是否已过期,因此您既不需要 public 密钥(用于非对称加密,如 RSA)或密钥(用于对称加密)像 AES 一样的加密)。 在 JWT 和 JWS 令牌中,声明只是纯文本 base64 编码,因此您可以 decode the payload without verifying if the signature is valid 并阅读过期声明。 如果您正在加密负载(又名使用 JWE),那么您将无法执行此操作。

来自 jjwt library

的注释

JWTs can be cryptographically signed (making it a JWS) or encrypted (making it a JWE).

Here 是来自 auth0 的 ligthweigth 库,用于解码 JWT/JWS 令牌 的 base64 编码声明。 一个人甚至在问 checking expiration.

我不知道你为什么认为你应该在客户端做这个控制,唯一的好处是避免发送客户端知道会失败的 API 请求。他们应该失败,因为服务器应该验证令牌没有过期,显然之前的签名验证(使用 secret/private 密钥)。

RFC 7519 对这个说法说:

The "exp" (expiration time) claim identifies the expiration time on or after which the JWT MUST NOT be accepted for processing.

在您所说的网络应用程序中,令牌的使用是为了允许无状态服务器对客户端请求进行身份验证。 OPTIONAL 过期声明的目标是允许服务器对生成的 JWS 有一定的控制权(如果我们使用 JWT 进行身份验证签名是必须的,所以我们应该谈论 JWS) .

没有过期,令牌将永远有效或直到用于签署它们的密钥发生更改(这将使验证过程失败)。 顺便说一句,invalidating sessions 是使用无状态身份验证最臭名昭著的缺点之一。

如果我们在用于授权的 JWS 负载(又名声明)中包含信息,例如用户具有哪些角色,会话失效将成为一个真正的问题。

来自Stop using JWT for sessions

but more seriously, it can also mean somebody has a token with a role of admin, even though you've just revoked their admin role. Because you can't invalidate tokens either, there's no way for you to remove their administrator access

过期控制并没有解决这个问题,我认为更倾向于避免会话劫持或 CSRF 攻击。

使用 CSRF 的攻击者将能够向您的 API 发出带有过期 JWS 的请求,从而跳过过期控制。

另一个问题是使用 public 或密钥验证客户端中的签名。

关于你的问题

I am using seems to require a public key to use it's verify() function. I don't seem to have a public key, only a secret, which I just made up, so it wasn't generated with a pair.

您指出的验证方法明确表示它接受 public 或密钥。

jwt.verify(token, secretOrPublicKey, [options, callback])

secretOrPublicKey is a string or buffer containing either the secret for HMAC algorithms, or the PEM encoded public key for RSA and ECDSA

我假设你两者都没有使用,你使用的是像 'shhhh'.

这样的字符串
var token = jwt.sign({ data: '¿Donde esta Santiago?'}, 'shhhh');

那你应该做

var decoded = jwt.verify(token, 'shhhhh');

但是,这里的问题是:真的需要客户端验签吗?

我认为不是,至少对于这种客户端仅使用 JWS 向服务器发送后续请求的应用程序来说不是:“嘿服务器,我是 Gabriel,我有一张纸(令牌)在这里保证,那份文件是由你签署的。” 因此,如果客户端未验证 JWS,并且 MITM 已成功向该客户端提供由他自己签名的 JWS(而不是服务器签名的 JWS),则后续请求将失败。 与过期控制一样,签名验证只是防止客户端发出会失败的请求。

现在,客户端验证需要发送 public 或密钥。 发送 public 密钥并不代表安全问题,但这是额外的工作和处理,几乎没有好处。

发送密钥(如 'shhhh')可能代表安全问题,因为用于签署令牌的密钥是相同的。

为了那些来到这里使用 public 密钥在浏览器上寻找 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

P.S。不要使用您的秘钥进行浏览器 jwt 验证! Public 仅密钥!

管理令牌何时在客户端过期,这样您就可以避免发送您知道会被拒绝的令牌,这纯粹是一种优化,可以避免与服务器的额外往返。这是一个完全有效的问题,但不是安全问题。由您决定是否需要该级别的优化。服务器必须验证令牌签名并拒绝过期的令牌作为安全问题。令牌不需要加密,除非它们包含您不希望最终用户或以某种方式获取令牌副本的攻击者可见的敏感数据。作为良好的安全实践,令牌应通过 HTTPS / SSL 传输。访问令牌通常是短暂的。如果您还使用刷新令牌,则永远不要将刷新令牌存储在浏览器中,除非它是由服务器为同源域设置的安全 cookie,并且浏览器脚本无法访问。在那种情况下,刷新令牌仍应定期轮换。