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】:我有同样的问题
在我的情况下,验证端点参数的问题是 email
和 password
而嵌套身份验证文档指出它们应该是username
和password
,如下所示
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 未授权的主要内容,如果未能解决你的问题,请参考以下文章
Nest.js 中使用 @nestjs/passport 的可选身份验证
Laravel 5.4 Tymon JWT Auth Guard 驱动未定义