JWT 来自一个网站的无效签名,但另一个网站没有错误。用户对象的额外属性被添加到令牌

Posted

技术标签:

【中文标题】JWT 来自一个网站的无效签名,但另一个网站没有错误。用户对象的额外属性被添加到令牌【英文标题】:JWT Invalid signature from one website, but no errors with another. Extra properties of user object get added to token 【发布时间】:2021-12-20 16:53:12 【问题描述】:

问题

我有两个网站。一个是生产,但是一个非常非常旧的版本。 另一个只是我目前正在测试它的本地主机。 在将NextAuthJS 加入 NextJS 网站后,我需要将我的 NextAuthJS 会话提供程序集成到远程 ExpressJS 后端。

    我向 /login 发出请求 ->

      在远程后端我生成一个 JWT 令牌,并获取完整的用户详细信息。 然后我将数据发送到 NextJS 网站。 使用 NextAuthJS,将其存储在 cookie 中。

    如果我需要获取任何用户特定的详细信息,例如保存的视频:

      我从 NextJS 网站发出请求。 在远程 ExpressJS 后端,我解码在“授权”承载中收到的令牌 标头,获取用户的详细信息...

然后繁荣!这里的错误信息重复对我大喊:“JsonWebTokenError: invalid signature”

这不会发生在生产网站上,即使远程后端是生产和开发的一个环节。

我已经尝试和探索的内容

我注意到使用JWT token debugger 是

    如果生产网站的请求没有给出任何错误,则解码的用户由 id, role 组成。正如预期的那样。 但如果本地版本的请求给出错误消息,则表明用户由存储在数据库中的整个用户详细信息对象组成。 并且调试器还会自动将解密算法从 HS256 更改为 HS512。而且,即使显示加密消息,它也会显示“令牌验证失败”。

来自本地的令牌,给出错误: eyJhbGciOiJIUzUxMiJ9.eyJlbWFpbCI6ImdtYWlsQGdtYWlsLmNvIiwiYWNjZXNzVG9rZW4iOiJCZWFyZXIgZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmZhV1FpT2lJMk1UZGpNV0V6TjJFd1lqQXpNakV4TnpFMU0yUTBOek1pTENKeWIyeGxJam9pZFhObGNpSXNJbWxoZENJNk1UWXpOakk1TURrME1Td2laWGh3SWpveE5qTTJNemMzTXpReGZRLmI1YTQ3bF9rcVQ5YUhDVGhFM3VtVFZJOTZZSnpkdkJaZHQxN3hMTFJPU1EiLCJ1c2VyIjp7Il9pZCI6IjYxN2MxYTM3YTBiMDMyMTE3MTUzZDQ3MyIsImZpcnN0TmFtZSI6IiIsImxhc3ROYW1lIjoiIiwiZW1haWwiOiJnbWFpbEBnbWFpbC5jbyIsInJvbGUiOiJ1c2VyIiwiam9iUm9sZSI6IiIsInZpZGVvR29hbCI6IiIsInByb2ZpbGVQaWN0dXJlIjoiIiwiYXBwcm92YWwiOnsiaXNBcHByb3ZlZCI6dHJ1ZSwidHJ4SWQiOiIifSwiYWNjZXNzVHlwZSI6eyJicmFuZGluZzEiOnRydWUsImJyYW5kaW5nMiI6dHJ1ZSwiYnJhbmRpbmczIjp0cnVlLCJicmFuZGluZzQiOnRydWUsInNjcmlwdCI6dHJ1ZSwidGVtcGxhdGUiOnRydWUsImZ1bGxBY2Nlc3MiOnRydWV9LCJyZXNldFBhc3NUb2tlbiI6IiIsImJyYW5kaW5nIjp7ImJyYW5kaW5nMSI6IjYxN2MxYTM3YTBiMDMyMTE3MTUzZDQ3NCIsImJyYW5kaW5nMiI6IjYxN2MxYTM3YTBiMDMyMTE3MTUzZDQ3NSIsImJyYW5kaW5nMyI6IjYxN2MxYTM3YTBiMDMyMTE3MTUzZDQ3NiIsImJyYW5kaW5nNCI6IjY xN2MxYTM3YTBiMDMyMTE3MTUzZDQ3NyJ9LCJzdWJfc3RhdHVzIjoyfSwiaWF0IjoxNjM2MjkxNTk0LCJleHAiOjE2Mzg4ODM1OTR9.ZCnnNZ2a7aeO6GCnyhnWYGMX-4kkJX75ZyzG52lSiWxUBIawXrA37882HFwo_3r6-I3JrrYNv270vxfsMwyBlw P>


生产中的令牌,按预期工作: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdjMWEzN2EwYjAzMjExNzE1M2Q0NzMiLCJyb2xlIjoidXNlciIsImlhdCI6MTYzNjI5MTY3MywiZXhwIjoxNjM2Mzc4MDczfQ.bqY6y0_lmX5dBAHemgv_9UFuupwLBBDcyFpAdXgCiH8 P>


JWT 秘密:jdgfaiodsugfaileufliaeruy


解码token并获取用户数据的代码为:

exports.requireSignin = (req, res, next) => 
  if (req.headers.authorization) 
    const token = req.headers.authorization.split(" ")[1];
    console.log( 'token is', token );
    jwt.verify(token, process.env.JWT_SECRET, function (err, decoded) 
      if (err) 
        console.log( err, 'Token given', token, 'Token secret', process.env.JWT_SECRET );
        return res.status(401).json( error: "Token has been expired!" );
      
      req.user = decoded;
      console.log( decoded );
    );
    // const user = jwt.verify(token, process.env.JWT_SECRET);
    // console.log(user);
    // req.user = user;
   else 
    return res.status(401).json( error: "Authorization required" );
  
  next();
  //jwt.decode()
;

生成该令牌的代码:

exports.signin = async (req, res) => 
  
  
  User.findOne( email: req.body.email ).exec((error, user) => 
    if (error) console.log( 'login error', error ); return res.status(400).json( error ); 
   
    if (user) 
      bcrypt.compare(req.body.password, user.hash_password, (err, result) => 
        if (err) 
            console.log( req.body.password, user.hash_password );
          return res
            .status(400)
            .json( error: "Something's wrong, Please try again" );
        
        if (!result) 
          return res.status(400).json( error: "Invalid credentials" );
        

        if (user.approval.isApproved === false)
          return res
            .status(400)
            .json( error: "Please verify your account" );

        if (user.isSuspended === true)
          return res.status(400).json( error: "You have been temporarily suspended, please contact support" );

        const token = jwt.sign(
           _id: user._id, role: user.role ,
          process.env.JWT_SECRET,
          
            expiresIn: "1d",
          
        );
        console.log( process.env.JWT_SECRET, token, user._id, user.role );

        // some unnecessary code ...

          res.status(200).json(
            success: true,
            token: "Bearer " + token,
            ProfileImageBaseUrl: ProfileImageBaseUrl,
            user: 
              _id,
              firstName,
              lastName,
              email,
              role,
              jobRole,
              videoGoal,
              profilePicture,
              createdBy,
              approval,
              accessType,
              resetPassToken,
              branding,
              sub_status
            ,
          );

           
            
        )
       
      );
     else 
      return res.status(400).json( error: "Something's wrong, Please try again" );
    
  );
;

A) 当我收到错误时,我从后端收到的日志是:


登录控制器,console.log(process.env.JWT_SECRET, token, user._id, user.role) 是

jdgfaiodsugfaileufliaeruy eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdjMWEzN2EwYjAzMjExNzE1M2Q0NzMiLCJyb2xlIjoidXNlciIsImlhdCI6MTYzNjI5MTY3MywiZXhwIjoxNjM2Mzc4MDczfQ.bqY6y0_lmX5dBAHemgv_9UFuupwLBBDcyFpAdXgCiH8 617c1a37a0b032117153d473用户 P>


需要登录控制器,console.log(err, 'Token given', token, 'Token secret', process.env.JWT_SECRET ) 是: p>

令牌给定eyJhbGciOiJIUzUxMiJ9.eyJlbWFpbCI6ImdtYWlsQGdtYWlsLmNvIiwiYWNjZXNzVG9rZW4iOiJCZWFyZXIgZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmZhV1FpT2lJMk1UZGpNV0V6TjJFd1lqQXpNakV4TnpFMU0yUTBOek1pTENKeWIyeGxJam9pZFhObGNpSXNJbWxoZENJNk1UWXpOakk1TURrME1Td2laWGh3SWpveE5qTTJNemMzTXpReGZRLmI1YTQ3bF9rcVQ5YUhDVGhFM3VtVFZJOTZZSnpkdkJaZHQxN3hMTFJPU1EiLCJ1c2VyIjp7Il9pZCI6IjYxN2MxYTM3YTBiMDMyMTE3MTUzZDQ3MyIsImZpcnN0TmFtZSI6IiIsImxhc3ROYW1lIjoiIiwiZW1haWwiOiJnbWFpbEBnbWFpbC5jbyIsInJvbGUiOiJ1c2VyIiwiam9iUm9sZSI6IiIsInZpZGVvR29hbCI6IiIsInByb2ZpbGVQaWN0dXJlIjoiIiwiYXBwcm92YWwiOnsiaXNBcHByb3ZlZCI6dHJ1ZSwidHJ4SWQiOiIifSwiYWNjZXNzVHlwZSI6eyJicmFuZGluZzEiOnRydWUsImJyYW5kaW5nMiI6dHJ1ZSwiYnJhbmRpbmczIjp0cnVlLCJicmFuZGluZzQiOnRydWUsInNjcmlwdCI6dHJ1ZSwidGVtcGxhdGUiOnRydWUsImZ1bGxBY2Nlc3MiOnRydWV9LCJyZXNldFBhc3NUb2tlbiI6IiIsImJyYW5kaW5nIjp7ImJyYW5kaW5nMSI6IjYxN2MxYTM3YTBiMDMyMTE3MTUzZDQ3NCIsImJyYW5kaW5nMiI6IjYxN2MxYTM3YTBiMDMyMTE3MTUzZDQ3NSIsImJyYW5kaW5nMyI6IjYxN2MxYTM3YTBiMDMyMTE3MTUzZDQ3NiIsImJy YW5kaW5nNCI6IjYxN2MxYTM3YTBiMDMyMTE3MTUzZDQ3NyJ9LCJzdWJfc3RhdHVzIjoyfSwiaWF0IjoxNjM2MjkxNTk0LCJleHAiOjE2Mzg4ODM1OTR9.ZCnnNZ2a7aeO6GCnyhnWYGMX-4kkJX75ZyzG52lSiWxUBIawXrA37882HFwo_3r6-I3JrrYNv270vxfsMwyBlw令牌秘密jdgfaiodsugfaileufliaeruy P>


B) 当没有错误时,这些是:


需要在符号强> em>的:令牌是eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdjMWEzN2EwYjAzMjExNzE1M2Q0NzMiLCJyb2xlIjoidXNlciIsImlhdCI6MTYzNjI5MTYyNSwiZXhwIjoxNjM2Mzc4MDI1fQ.K-inQfI77mepKjkG_5g4obr3sfcVnDwCOXeQlDPra00 P>


登录

jdgfaiodsugfaileufliaeruy eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdjMWEzN2EwYjAzMjExNzE1M2Q0NzMiLCJyb2xlIjoidXNlciIsImlhdCI6MTYzNjI5MTYyNSwiZXhwIjoxNjM2Mzc4MDI1fQ.K-inQfI77mepKjkG_5g4obr3sfcVnDwCOXeQlDPra00 617c1a37a0b032117153d473用户 P>

我的想法

我觉得这只是一些我没有注意到的简单问题,我已经尝试修复了 9 个小时,但我还没有确定还有什么。

请帮帮我,伙计们!

非常感谢任何帮助或提示!

非常感谢您!

【问题讨论】:

您需要找到生成这些错误标记的代码。显然不是jwt.sign( _id: user._id, role: user.role , process.env.JWT_SECRET, expiresIn: "1d"); 你确定这个秘密在开发阶段是正确的吗? @eol,首先,非常感谢您的回复!是的,这个秘密是完全正确的,但我在 next-auth-js-specific 中发现了问题。 @Bergi,非常感谢您的回复!看来我从远程后端获取的令牌 NextAuthJS 正在散列,所以有双重加密。我从使用 cookie 中的 jwt 令牌转移到使用从 db 获得的用户对象中存储的令牌 【参考方案1】:

感谢所有帮助我的人! @Bergi 和 @eol!

问题是 nextauthjs 特有的,与 expressjs 无关,正如它所出现的那样。

所以我不再得到 nextauthjs 返回的令牌了。

相反,我从用户对象的 authToken 属性中获取令牌,存储在会话中。


原来是这样的:

MyApp.getInitialProps = async ( router, ctx ) => 
  const session = await getSession(ctx);
  if (router.asPath === '/login' || router.asPath === '/register') 
    return  session: session ;
  
  if (!session || !session.user) 
    ctx.res.writeHead(302, 
      Location: '/login',
    );
    ctx.res.end();
  
  console.log('Access token', session.accessToken);
  axios.defaults.headers.common = 
    authorization: `$session.accessToken`,
  ;
  return  token: `$session.accessToken`, session: session ;
;

【讨论】:

以上是关于JWT 来自一个网站的无效签名,但另一个网站没有错误。用户对象的额外属性被添加到令牌的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 JWT.IO 网站创建 RS256 JWT?

如何生成与 JWT.IO 网站相同的签名?

使用 JWT PHP 库导致“签名验证失败”的网站的 Google 登录

用于向 Google API (Swift) 验证服务帐户的 JWT 签名无效

JWT/OAuth 令牌签名验证失败

SSL证书无效是啥原因?