简体   繁体   English

使用 Guards(角色,JWT)获取用户数据

[英]Getting User Data by using Guards (Roles, JWT)

The documentation is kinda thin here so I ran into a problem.这里的文档有点薄,所以我遇到了一个问题。 I try to use Guards to secure Controller or it's Actions, so I gonna ask for the role of authenticated requests (by JWT).我尝试使用 Guards 来保护 Controller 或它的 Actions,所以我会要求经过身份验证的请求的作用(通过 JWT)。 In my auth.guard.ts I ask for "request.user" but it's empty, so I can't check the users role.在我的 auth.guard.ts 中,我要求“request.user”但它是空的,所以我无法检查用户角色。 I don't know how to define "request.user".我不知道如何定义“request.user”。 Here is my auth module and it's imports.这是我的 auth 模块和它的导入。

auth.controller.ts 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角色.guard.ts

Here user.request is empty, because I never define it.这里 user.request 是空的,因为我从来没有定义过它。 The documentation doesn't show how or where.文档没有显示如何或在哪里。

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 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 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 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;
  }
}

Documentation: https://docs.nestjs.com/guards文档: https : //docs.nestjs.com/guards

Thanks for any help.谢谢你的帮助。

Additionally to your RolesGuard you need to use an AuthGuard .除了RolesGuard您还需要使用AuthGuard

Standard标准

You can use the standard AuthGuard implementation which attaches the user object to the request.您可以使用将用户对象附加到请求的标准AuthGuard实现。 It throws a 401 error, when the user is unauthenticated.当用户未经身份验证时,它会引发 401 错误。

@UseGuards(AuthGuard('jwt'))

Extension延期

If you need to write your own guard because you need different behavior, extend the original AuthGuard and override the methods you need to change ( handleRequest in the example):如果您因为需要不同的行为而需要编写自己的守卫,请扩展原始AuthGuard并覆盖您需要更改的方法(示例中的handleRequest ):

@Injectable()
export class MyAuthGuard extends AuthGuard('jwt') {

  handleRequest(err, user, info: Error) {
    // don't throw 401 error when unauthenticated
    return user;
  }

}

Why do this?为什么要这样做?

If you look at the source code of the AuthGuard you can see that it attaches the user to the request as a callback to the passport method.如果您查看AuthGuard源代码,您会发现它将用户附加到请求中,作为对通行证方法的回调。 If you don't want to use/extend the AuthGuard , you will have to implement/copy the relevant parts.如果您不想使用/扩展AuthGuard ,则必须实现/复制相关部分。

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;

You can attach multiple guards together (@UseGuards(AuthGuard('jwt'), RolesGuard)) to pass the context between them.您可以将多个守卫连接在一起(@UseGuards(AuthGuard('jwt'), RolesGuard))以在它们之间传递上下文。 Then you will have access 'req.user' object inside 'RolesGuard'.然后您将可以访问“RolesGuard”中的“req.user”对象。

Does it work if you use req.authInfo ?如果您使用req.authInfo是否req.authInfo

As long as you don't provide a custom callback to passport.authenticate method, the user data should be attached to the request object like this.只要您不为passport.authenticate 方法提供自定义回调,用户数据就应该像这样附加到请求对象上。

req.authInfo should be the object you returned in your validate method req.authInfo应该是您在validate方法中返回的对象

After I got the selected answer working (thank you), I found this option as well that you can add to the constructor that essentially does the same thing.在我得到选定的答案后(谢谢),我也找到了这个选项,你可以将它添加到基本上做同样事情的构造函数中。

http://www.passportjs.org/docs/authorize/ 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.为避免这种情况,请将策略的 passReqToCallback 选项设置为 true。 With this option enabled, req will be passed as the first argument to the verify callback.启用此选项后, req 将作为第一个参数传递给验证回调。

    @Injectable()
    export class LocalStrategy extends PassportStrategy(Strategy, 'local') {
    
        constructor(private authService: AuthService) {
            super({
                passReqToCallback: true
            })
        }

        // rest of the strategy (validate)
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM