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 PHP 库导致“签名验证失败”的网站的 Google 登录