当我使用旧的刷新令牌时,为什么刷新端点返回新令牌?

时间:2020-10-21 20:03:57

标签: javascript node.js express jwt bcrypt

您好,我使用npm中的这两个库:bcryptjsonwebtoken

我创建了/refresh-token端点,并希望生成新的一对访问和刷新令牌。因此,我将刷新令牌发送到端点并进行检查(如果不为null),从令牌声明中保存的数据中获取数据库中的用户,并将请求中的刷新令牌与存储在数据库中的哈希刷新令牌进行比较。如果isMatch为真,则生成一对新的令牌,并使用bcrypt从刷新令牌创建哈希。接下来,我将此哈希保存到数据库中,并将新对返回给用户。但是,当我发送与以前相同的刷新令牌时,仍然可以获得新的一对,如果我发送访问令牌而不是刷新令牌isMatch返回true,则获得新的一对令牌。哈希是由不同的jwt(不同的字符串)创建的,那么如何在比较中始终保持为真?

import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
import { User } from '../entities/user.model';
import { getRepository } from 'typeorm';
import { InternalServerException } from '../exceptions/exceptions';

interface AccessPayload {
  email: string;
  role: string;
};

export class JwtTokenProvider {

  private readonly jwtSecret: string = process.env.JWT_SECRET as string;

  generateAccessTokens = async (user: User) => {
    if (!user) {
      throw new InternalServerException('Failed to generate JWT Token - User is null');
    };
    const accessPayload: AccessPayload = {email: user.email, role: user.role as string};
    const accessToken: string = jwt.sign(accessPayload, this.jwtSecret, {
      expiresIn:  process.env.TOKEN_EXP_TIME as string
    });
    const refreshToken: string = jwt.sign(accessPayload, this.jwtSecret, {
      expiresIn: process.env.REFRESH_EXP_TIME as string
    });
    this.saveRefreshToken(user, refreshToken);
    return { accessToken, refreshToken };
  };

  getUserFromToken = async (token: string): Promise<User | undefined>  => {
    const tokenPayload = await jwt.verify(token, this.jwtSecret) as AccessPayload;
    return await getRepository(User).findOne({ email: tokenPayload.email }, { relations: ['groupScope'] });
  };

  compareRefreshTokens = async (plainToken: string, hashToken: string): Promise<boolean> => {
    return await bcrypt.compare(plainToken, hashToken);
  };

  private saveRefreshToken = async (user: User, refreshToken: string): Promise<any> => {
    user.refreshToken = await this.encodeRefreshToken(refreshToken);
    await getRepository(User).save(user);
  };

  private encodeRefreshToken = async (refreshToken: string): Promise<string> => {
    return await bcrypt.hash(refreshToken, 12);
  }
};

public refreshToken = async (request: Request, response: Response, next: NextFunction) => {
    try {
     const requestToken = request.body.refresh_token as string;
      if (!requestToken) {
        return next(new BadRequestException('Refresh token cannot be empty'));
      }
      const user = await this.jwtProvider.getUserFromToken(requestToken);
      if (!user) {
        return next(new AuthorizedException('Cannot get user from token'));
      }
      const isMatch = await this.jwtProvider.compareRefreshTokens(requestToken, user.refreshToken);
      if(!isMatch) {
        return next(new AuthorizedException('Refresh token is incorrect'));
      }
      const { accessToken, refreshToken } = await this.jwtProvider.generateAccessTokens(user);
      response.status(200).send({ accessToken, refreshToken });
    } catch (error) {
        return next(new Error(error.message));
      };
  };

1 个答案:

答案 0 :(得分:1)

来自bcrypt repo page

<块引用>

最大输入长度为 72 个字节(注意 UTF8 编码的字符最多使用 4 个字节),生成的哈希长度为 60 个字符。

这意味着只使用字符串的前 72 个字节。匹配刷新标记时任何额外的字节都会被忽略(注意这不是前 72 个字符)。由于匹配了 72 个字节,因此在使用旧令牌时会得到成功响应。

一种解决方法是先使用 SHA-256 对字符串进行哈希处理,然后使用 bcrypt。

@bot.command()
async def map(ctx, *args):
    await ctx.channel.send('{} arguments: {}'.format(len(args), ', '.join(args)))

然后(可能在另一个文件中):

import { createHash } from 'crypto';

const hash = createHash('sha256').update(user.refreshToken).digest('hex');
user.refreshToken = bcrypt.hashSync(hash, salt);