使用Guards(Roles、JWT)获取用户数据
Getting User Data by using Guards (Roles, JWT)
这里的文档有点薄,所以我 运行 遇到了问题。我尝试使用 Guards 来保护 Controller 或它的 Actions,所以我会询问经过身份验证的请求的角色(通过 JWT)。在我的 auth.guard.ts 中,我要求 "request.user" 但它是空的,所以我无法检查用户角色。我不知道如何定义"request.user"。这是我的身份验证模块,它是导入的。
auth.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { RolesGuard } from './auth.guard';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Get('token')
async createToken(): Promise<any> {
return await this.authService.signIn();
}
@Get('data')
@UseGuards(RolesGuard)
findAll() {
return { message: 'authed!' };
}
}
roles.guard.ts
这里user.request是空的,因为我从来没有定义过它。该文档没有显示如何或在哪里。
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user; // it's undefined
const hasRole = () =>
user.roles.some(role => !!roles.find(item => item === role));
return user && user.roles && hasRole();
}
}
auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { HttpStrategy } from './http.strategy';
import { UserModule } from './../user/user.module';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secretOrPrivateKey: 'secretKey',
signOptions: {
expiresIn: 3600,
},
}),
UserModule,
],
providers: [AuthService, HttpStrategy],
controllers: [AuthController],
})
export class AuthModule {}
auth.service.ts
import { Injectable } from '@nestjs/common';
import { UserService } from '../user/user.service';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(
private readonly userService: UserService,
private readonly jwtService: JwtService,
) {}
async signIn(): Promise<object> {
// In the real-world app you shouldn't expose this method publicly
// instead, return a token once you verify user credentials
const user: any = { email: 'user@email.com' };
const token: string = this.jwtService.sign(user);
return { token };
}
async validateUser(payload: any): Promise<any> {
// Validate if token passed along with HTTP request
// is associated with any registered account in the database
return await this.userService.findOneByEmail(payload.email);
}
}
jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './auth.service';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'secretKey',
});
}
async validate(payload: any) {
const user = await this.authService.validateUser(payload);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
文档:https://docs.nestjs.com/guards
感谢您的帮助。
如果使用 req.authInfo
是否有效?
只要您不向 passport.authenticate 方法提供自定义回调,用户数据就应该像这样附加到请求对象。
req.authInfo
应该是您在 validate
方法中返回的对象
除了 RolesGuard
,您还需要使用 AuthGuard
。
标准
您可以使用将用户对象附加到请求的标准 AuthGuard
实现。当用户未经身份验证时,它会抛出 401 错误。
@UseGuards(AuthGuard('jwt'))
分机
如果因为需要不同的行为而需要编写自己的守卫,请扩展原始 AuthGuard
并覆盖您需要更改的方法(示例中的 handleRequest
):
@Injectable()
export class MyAuthGuard extends AuthGuard('jwt') {
handleRequest(err, user, info: Error) {
// don't throw 401 error when unauthenticated
return user;
}
}
为什么要这样做?
如果您查看 AuthGuard
的 source code,您会发现它将用户附加到请求,作为对 passport 方法的回调。如果您不想 use/extend AuthGuard
,则必须 implement/copy 相关部分。
const user = await passportFn(
type || this.options.defaultStrategy,
options,
// This is the callback passed to passport. handleRequest returns the user.
(err, info, user) => this.handleRequest(err, info, user)
);
// Then the user object is attached to the request
// under the default property 'user' which you can change by configuration.
request[options.property || defaultOptions.property] = user;
您可以将多个守卫附加在一起 (@UseGuards(AuthGuard('jwt'), RolesGuard)) 以在它们之间传递上下文。然后您将可以访问 'RolesGuard'.
中的 'req.user' 对象
在我得到选定的答案后(谢谢),我也找到了这个选项,你可以将它添加到本质上做同样事情的构造函数中。
http://www.passportjs.org/docs/authorize/
Association in Verify Callback
One downside to the approach described above is that it requires two
instances of the same strategy and supporting routes.
To avoid this, set the strategy's passReqToCallback option to true.
With this option enabled, req will be passed as the first argument to
the verify callback.
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
constructor(private authService: AuthService) {
super({
passReqToCallback: true
})
}
// rest of the strategy (validate)
}
这里的文档有点薄,所以我 运行 遇到了问题。我尝试使用 Guards 来保护 Controller 或它的 Actions,所以我会询问经过身份验证的请求的角色(通过 JWT)。在我的 auth.guard.ts 中,我要求 "request.user" 但它是空的,所以我无法检查用户角色。我不知道如何定义"request.user"。这是我的身份验证模块,它是导入的。
auth.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { RolesGuard } from './auth.guard';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Get('token')
async createToken(): Promise<any> {
return await this.authService.signIn();
}
@Get('data')
@UseGuards(RolesGuard)
findAll() {
return { message: 'authed!' };
}
}
roles.guard.ts
这里user.request是空的,因为我从来没有定义过它。该文档没有显示如何或在哪里。
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user; // it's undefined
const hasRole = () =>
user.roles.some(role => !!roles.find(item => item === role));
return user && user.roles && hasRole();
}
}
auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { HttpStrategy } from './http.strategy';
import { UserModule } from './../user/user.module';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secretOrPrivateKey: 'secretKey',
signOptions: {
expiresIn: 3600,
},
}),
UserModule,
],
providers: [AuthService, HttpStrategy],
controllers: [AuthController],
})
export class AuthModule {}
auth.service.ts
import { Injectable } from '@nestjs/common';
import { UserService } from '../user/user.service';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(
private readonly userService: UserService,
private readonly jwtService: JwtService,
) {}
async signIn(): Promise<object> {
// In the real-world app you shouldn't expose this method publicly
// instead, return a token once you verify user credentials
const user: any = { email: 'user@email.com' };
const token: string = this.jwtService.sign(user);
return { token };
}
async validateUser(payload: any): Promise<any> {
// Validate if token passed along with HTTP request
// is associated with any registered account in the database
return await this.userService.findOneByEmail(payload.email);
}
}
jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './auth.service';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: 'secretKey',
});
}
async validate(payload: any) {
const user = await this.authService.validateUser(payload);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
文档:https://docs.nestjs.com/guards
感谢您的帮助。
如果使用 req.authInfo
是否有效?
只要您不向 passport.authenticate 方法提供自定义回调,用户数据就应该像这样附加到请求对象。
req.authInfo
应该是您在 validate
方法中返回的对象
除了 RolesGuard
,您还需要使用 AuthGuard
。
标准
您可以使用将用户对象附加到请求的标准 AuthGuard
实现。当用户未经身份验证时,它会抛出 401 错误。
@UseGuards(AuthGuard('jwt'))
分机
如果因为需要不同的行为而需要编写自己的守卫,请扩展原始 AuthGuard
并覆盖您需要更改的方法(示例中的 handleRequest
):
@Injectable()
export class MyAuthGuard extends AuthGuard('jwt') {
handleRequest(err, user, info: Error) {
// don't throw 401 error when unauthenticated
return user;
}
}
为什么要这样做?
如果您查看 AuthGuard
的 source code,您会发现它将用户附加到请求,作为对 passport 方法的回调。如果您不想 use/extend AuthGuard
,则必须 implement/copy 相关部分。
const user = await passportFn(
type || this.options.defaultStrategy,
options,
// This is the callback passed to passport. handleRequest returns the user.
(err, info, user) => this.handleRequest(err, info, user)
);
// Then the user object is attached to the request
// under the default property 'user' which you can change by configuration.
request[options.property || defaultOptions.property] = user;
您可以将多个守卫附加在一起 (@UseGuards(AuthGuard('jwt'), RolesGuard)) 以在它们之间传递上下文。然后您将可以访问 'RolesGuard'.
中的 'req.user' 对象在我得到选定的答案后(谢谢),我也找到了这个选项,你可以将它添加到本质上做同样事情的构造函数中。
http://www.passportjs.org/docs/authorize/
Association in Verify Callback
One downside to the approach described above is that it requires two instances of the same strategy and supporting routes.
To avoid this, set the strategy's passReqToCallback option to true. With this option enabled, req will be passed as the first argument to the verify callback.
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
constructor(private authService: AuthService) {
super({
passReqToCallback: true
})
}
// rest of the strategy (validate)
}