Nest.js Auth Guard JWT 身份验证不断返回 401 未授权

Posted

技术标签:

【中文标题】Nest.js Auth Guard JWT 身份验证不断返回 401 未授权【英文标题】:Nest.js Auth Guard JWT Authentication constantly returns 401 unauthorized 【发布时间】:2020-10-29 03:41:59 【问题描述】:

使用 Postman 测试我的端点,我能够成功“登录”并接收 JWT 令牌。现在,我正在尝试访问一个应该具有 AuthGuard 的端点,以确保我现在已经登录,现在可以访问它。

但是,即使在 Postman 中提供 JWT 令牌,它也会不断返回 401 Unauthorized

这是我的代码:

user.controller.ts

@Controller('users')
export class UsersController 
    constructor(private readonly usersService: UsersService) 

    @UseGuards(AuthGuard())
    @Get()
    getUsers() 
        return this.usersService.getUsersAsync();
    

jwt.strategy.ts

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) 
    constructor(
        private readonly authenticationService: AuthenticationService,
    ) 
        super(
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            ignoreExpiration: false,
            secretOrKey: 'SuperSecretJWTKey',
        );
    

    async validate(payload: any, done: Function) 
        console.log("I AM HERE"); // this never gets called.
        const user = await this.authenticationService.validateUserToken(payload);

        if (!user) 
            return done(new UnauthorizedException(), false);
        

        done(null, user);
    

我也尝试过ExtractJWT.fromAuthHeaderWithScheme('JWT'),但这不起作用。

authentication.module.ts

@Module(
    imports: [
        ConfigModule,
        UsersModule,
        PassportModule.register( defaultStrategy: 'jwt' ),
        JwtModule.register(
            secret: 'SuperSecretJWTKey',
            signOptions:  expiresIn: 3600 ,
        ),
    ],
    controllers: [AuthenticationController],
    providers: [AuthenticationService, LocalStrategy, JwtStrategy],
    exports: [AuthenticationService, LocalStrategy, JwtStrategy],
)
export class AuthenticationModule 

authentication.controller.ts

@Controller('auth')
export class AuthenticationController 
    constructor(
        private readonly authenticationService: AuthenticationService,
        private readonly usersService: UsersService,
    ) 

    @UseGuards(AuthGuard('local'))
    @Post('login')
    public async loginAsync(@Response() res, @Body() login: LoginModel) 
        const user = await this.usersService.getUserByUsernameAsync(login.username);

        if (!user) 
            res.status(HttpStatus.NOT_FOUND).json(
                message: 'User Not Found',
            );
         else 
            const token = this.authenticationService.createToken(user);
            return res.status(HttpStatus.OK).json(token);
        
    

在 Postman 中,我可以使用我的登录端点以正确的凭据成功登录并接收 JWT 令牌。然后,我在 GET 请求中添加了一个 Authentication 标头,复制并粘贴到 JWT 令牌中,我尝试了“Bearer”和“JWT”两种方案,并且都返回 401 Unauthorized,如下图所示。

我使用了 JWT.IO 调试器来检查我的令牌是否有任何问题并且它看起来是正确的:

我不知道这可能是什么问题。任何帮助将不胜感激。

【问题讨论】:

问题可能出在 Postman 的请求中。尝试创建新请求并小心您在标头中放置的内容。如果您使用的是不记名令牌,请将其放在身份验证部分,而不是标题中。或者将其放在标题中,而不是在 auth 部分。做一些实验,会有帮助的。 【参考方案1】:

我的问题是我使用 RS256 算法来签署 JWT,并且出现“无效算法”错误。

所以我将“RS256”添加到我的“jwtStrategy”构造函数中,现在它看起来像这样:

constructor(private configService: ConfigService) 
super(
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  ignoreExpiration: false,
  algorithms:["RS256"],
  secretOrKey: configService.get('jwtPublicKey'),
);

然后它给了我一个错误,抱怨我的公钥文件上“没有起始行”,错误是我有一个 ssh-rsa 密钥格式而不是 rsa-pem 格式,我这样解决了:

Get PEM file from ssh-rsa key pair

终于成功了。

我得到了所有这些信息,在策略输出和守卫输出之间放置了一个记录器,这样做:

JWT Auth Guard example

【讨论】:

【参考方案2】:

我有同样的问题

在我的情况下,验证端点参数的问题是 emailpassword 而嵌套身份验证文档指出它们应该是usernamepassword,如下所示

async validate(username: string, password: string): Promise<any> 
    const user = await this.authService.validateUser(username, password);
    if (!user) 
      throw new UnauthorizedException();
    
    return user;
  

还要注意在请求正文中发送用户名和密码

学分: https://github.com/nestjs/docs.nestjs.com/issues/875#issuecomment-619472086

【讨论】:

【参考方案3】:

我有类似的 401 状态。我的问题是令牌到期时间非常短(60 秒)。在测试 jwt 时也要确保有一个合理的有效期。

【讨论】:

【参考方案4】:

我遇到了完全相同的问题。我的问题是 JwtModule secret 和 JwtStrategy secretOrKey 不同。希望这可以帮助那些坚持这一点的人!

【讨论】:

我也有同样的问题!与 .env 未正确加载有关。这是我注意到的第三个与此相关的问题,但仍然无法弄清楚。【参考方案5】:

请注意,您的 JWT 策略中的 validate() 函数仅在成功验证 JWT 之后调用。如果您在尝试使用 JWT 时始终收到 401 响应,那么您不能指望调用此函数。

validate() 方法中的return 被注入到任何受 JWT 身份验证保护的操作的请求对象中。

我不确定您正在调用的 done() 函数,但这是我当前项目中的一个有效的 validate() 方法:

async validate(payload: JwtPayload): Promise<User> 
  const  email  = payload
  const user = await this.authService.getActiveUser(email)

  if (!user) 
    throw new UnauthorizedException()
  

  return user

看起来您在返回用户的愿望上走在了正确的轨道上。确保这就是 authenticationService.validateUserToken() 实际所做的。

在策略中,jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken() 似乎是正确的,而在 Postman 中使用带有 Bearer TOKEN 的 Authorization 标头也看起来正确。

关于您的 authentication.controller.ts 文件,请注意在 NestJS 的控制器中直接使用 @Request@Response 对象。这些访问底层框架,例如Express 并且有可能绕过 Nest 实现的许多功能。请参阅 https://docs.nestjs.com/faq/request-lifecycle 了解您要跳过的内容...

您可以直接从 NestJS 中的装饰控制器方法(例如 @Get()Post() 等)返回对象并抛出错误,框架将处理其余部分:HTTP 代码、JSON 等。

从您的控制器考虑放弃@Reponse res 并改用throw new UnauthorizedException('User Not Found') 和简单的return token (或类似)方法。

在您的受保护路由中,我发现明确声明 AuthGuard('jwt') 效果更好,并且在某些情况下不会产生警告,即使您确实将默认策略设置为 JWT。

您的登录路径上真的需要AuthGuard('local') 吗?

在您的 loginAsync() 方法中,不要忘记使用有效负载实际签署令牌的关键步骤。您没有在身份验证服务中提供 createToken() 方法实现的代码,但我怀疑这可能是您所缺少的。

考虑一下登录服务的这个工作实现(由它的控制器的登录函数简单地调用):

  async login(authCredentialsDto: AuthCredentialsDto): Promise< accessToken: string > 
    const  email, password  = authCredentialsDto

    const success = await this.usersRepository.verifyCredentials(email, password)

    if (!success) 
      throw new UnauthorizedException('Invalid credentials')
    

    // roles, email, etc can be added to the payload - but don't add sensitive info!
    const payload: JwtPayload =  email  
    const accessToken = this.jwtService.sign(payload)

    this.logger.debug(`Generated JWT token with payload $JSON.stringify(payload)`)

    return  accessToken 
  

请注意,jwtService 通过将private jwtService: JwtService 添加到构造函数参数中,通过依赖注入注入到类中。

还要注意上面的接口是如何为JwtPayload 定义的,因此它是显式类型的。这比在代码中使用 any 更好。

最后,如果您的 JWT 仍未验证,请绝对确定您在 Postman 中正确使用您的令牌。 非常小心,不要添加前导/尾随空格、换行符等。我自己也犯了这个错误。您可能希望通过编写一个快速的 JS 文件来尝试您的 API 并发出一个获取请求,该请求将 Authorization 标头设置为值 Bearer $token

我希望这会有所帮助,祝你好运!

【讨论】:

谢谢。这是一个很大的帮助。发生的事情是我正在使用一个名为 jsonwebtoken 的包并执行类似 import * as jwt from 'jsonwebtoken' 后跟 jwt.sign(...) 的操作。当使用来自@nestjs/jwt 的实际JwtService 时,修复了它。 我很高兴这有帮助,当然使用实际的@nestjs/jwt 是采用这种方法的方法!干杯!

以上是关于Nest.js Auth Guard JWT 身份验证不断返回 401 未授权的主要内容,如果未能解决你的问题,请参考以下文章

没有数据库的 Laravel 中的 JWT 身份验证

Nest.js 中使用 @nestjs/passport 的可选身份验证

Laravel 5.4 Tymon JWT Auth Guard 驱动未定义

Nest.js 与 AWS Cognito,如何访问用户属性

jwt+nest.js,实现登录挤出功能

如何在没有守卫装饰器的情况下始终验证 JWT? (Nest.js + 护照)