首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

认证

身份验证是大多数现有应用程序的重要部分。有许多不同的方法,策略和方法来处理用户授权。我们最终决定使用的内容取决于特定的应用要求,并与他们的需求密切相关。

Passport是最受欢迎的node.js身份验证库,由社区众所周知,并在许多生产应用程序中相继使用。使用专用的护照实用程序将此工具与Nest框架集成非常简单。出于演示目的,我们将设置passport-http-bearer和passport-jwt策略。

安装

为了开始使用这个库的冒险,我们必须安装一些基本的包。此外,我们将首先实施承载策略,因此我们需要安装passport-http-bearer包。

代码语言:javascript
复制
$ npm install --save @nestjs/passport passport passport-http-bearer

持票人策略

如前所述,首先,我们将实施passport-http-bearer库。承载令牌通常用于保护API端点,通常使用OAuth 2.0发布。HTTP承载认证策略使用承载令牌对用户进行认证。

让我们从创建AuthService将公开单个方法的类开始,该方法的validateUser()职责是使用提供的承载令牌查询用户。

auth.service.ts

JS

代码语言:javascript
复制
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';

@Injectable()
export class AuthService {
  constructor(private readonly usersService: UsersService) {}

  async validateUser(token: string): Promise<any> {
    // Validate if token passed along with HTTP request
    // is associated with any registered account in the database
    return await this.usersService.findOneByToken(token);
  }
}

validateUser()方法token作为参数。此标记从Authorization已与HTTP请求一起传递的标头中提取。该findOneByToken()方法的职责是验证传递的令牌是否真正存在并且与数据库中的任何已注册帐户相关联。

一旦AuthService类完成后,我们必须创建一个相应的策略,即护照将用于验证请求。

http.strategy.ts

JS

代码语言:javascript
复制
import { Strategy } from 'passport-http-bearer';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';

@Injectable()
export class HttpStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService) {
    super();
  }

  async validate(token: string) {
    const user = await this.authService.validateUser(token);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

用于验证令牌的HttpStrategy用途AuthService。当令牌有效时,护照允许进一步的请求处理。否则,用户收到401 Unauthorized响应。

之后,我们可以创建了AuthModule

auth.module.ts

JS

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { HttpStrategy } from './http.strategy';
import { UsersModule } from '../users/users.module';

@Module({
  imports: [UsersModule],
  providers: [AuthService, HttpStrategy],
})
export class AuthModule {}

注意为了使用UsersServiceAuthModule进口UsersModule。内部实施在这里并不重要,在很大程度上取决于您的技术项目要求(例如数据库)。

然后,您只需使用AuthGuard您想要启用身份验证的任何位置即可。

代码语言:javascript
复制
@Get('users')
@UseGuards(AuthGuard('bearer'))
findAll() {
  return [];
}

@AuthGuard()从进口@nestjs/passport包。此外,bearer是护照将使用的策略的名称。让我们检查端点是否有效保护。为了确保一切正常,我们将在users不设置有效令牌的情况下对资源执行GET请求。

代码语言:javascript
复制
$ curl localhost:3000/users

应用程序应响应401 Unauthorized状态代码和以下响应正文:

代码语言:javascript
复制
"statusCode": 401,
"error": "Unauthorized"

如果您事先创建了有效令牌并将其与HTTP请求一起传递,则应用程序将分别标识用户,将其对象附加到请求,并允许进一步的请求处理。

代码语言:javascript
复制
$ curl localhost:3000/users -H "Authorization: Bearer TOKEN"

默认策略

要确定默认护照行为,您可以注册PassportModule

auth.module.ts

JS

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { HttpStrategy } from './http.strategy';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '@nestjs/passport';

@Module({
  imports: [
    PassportModule.register({ defaultStrategy: 'bearer' }),
    UsersModule,
  ],
  providers: [AuthService, HttpStrategy],
})
export class AuthModule {}

设置完成后defaultStrategy,您不再需要在@AuthGuard()装饰器中手动传递策略名称。

代码语言:javascript
复制
@Get('users')
@UseGuards(AuthGuard())
findAll() {
  return [];
}

用户对象

当正确验证请求时,用户实体将附加到请求对象并可通过user属性访问(例如req.user)。要更改属性名称,请设置property选项对象。

代码语言:javascript
复制
PassportModule.register({ property: 'profile' })

定制护照

根据正在使用的策略,护照会采用一系列影响库行为的属性。使用register()方法将选项对象直接传递给护照实例。

代码语言:javascript
复制
PassportModule.register({ session: true })

遗产

在大多数情况下,AuthGuard就足够了。但是,为了调整默认错误处理或身份验证逻辑,您可以在子类中扩展类和覆盖方法。

代码语言:javascript
复制
import {
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  canActivate(context: ExecutionContext) {
    // Add your custom authentication logic here
    // for example, call super.logIn(request) to establish a session.
    return super.canActivate(context);
  }

  handleRequest(err, user, info) {
    if (err || !user) {
      throw err || new UnauthorizedException();
    }
    return user;
  }
}

JWT战略

第二种描述的方法是使用JSON Web令牌(JWT)验证端点。要实现基于JWT的身份验证流程,我们需要安装所需的包。

代码语言:javascript
复制
$ npm install --save @nestjs/jwt passport-jwt

安装过程完成后,我们可以专注于AuthService课程。我们需要从令牌验证切换到基于有效负载的验证逻辑,并提供为特定用户创建JWT令牌的方法,然后可以使用该方法验证传入请求。

auth.service.ts

JS

代码语言:javascript
复制
import { JwtService } from '@nestjs/jwt';
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { JwtPayload } from './interfaces/jwt-payload.interface';

@Injectable()
export class AuthService {
  constructor(
    private readonly usersService: UsersService,
    private readonly jwtService: JwtService,
  ) {}

  async signIn(): Promise<string> {
    // In the real-world app you shouldn't expose this method publicly
    // instead, return a token once you verify user credentials
    const user: JwtPayload = { email: 'user@email.com' };
    return this.jwtService.sign(user);
  }

  async validateUser(payload: JwtPayload): Promise<any> {
    return await this.usersService.findOneByEmail(payload.email);
  }
}

提示JwtPayload是一个具有单个属性的接口email,表示已解码的JWT令牌。

为了简化示例,我们创建了一个假用户。第二步是创建一个对应的JwtStrategy

jwt.strategy.ts

JS

代码语言:javascript
复制
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './auth.service';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtPayload } from './interfaces/jwt-payload.interface';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: 'secretKey',
    });
  }

  async validate(payload: JwtPayload) {
    const user = await this.authService.validateUser(payload);
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  }
}

用于验证解码的有效负载的JwtStrategy用途AuthService。当有效负载有效(用户存在)时,护照允许进一步的请求处理。否则,用户收到401 (Unauthorized)响应。

之后,我们可以搬到了AuthModule

auth.module.ts

JS

代码语言:javascript
复制
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '@nestjs/passport';

@Module({
  imports: [
    PassportModule.register({ defaultStrategy: 'jwt' }),
    JwtModule.register({
      secretOrPrivateKey: 'secretKey',
      signOptions: {
        expiresIn: 3600,
      },
    }),
    UsersModule,
  ],
  providers: [AuthService, JwtStrategy],
})
export class AuthModule {}

提示为了利用UsersServiceAuthModule进口UsersModule。内部实施在这里并不重要。此外,JwtModule已经静态注册。要切换到异步配置,请在此处阅读更多内容。

到期时间secretKey都是硬编码的(在实际应用中,您应该考虑使用环境变量)。

然后,您只需使用AuthGuard您想要启用身份验证的任何位置即可。

代码语言:javascript
复制
@Get('users')
@UseGuards(AuthGuard())
findAll() {
  return [];
}

让我们检查端点是否有效保护。为了确保一切正常,我们将在users不设置有效令牌的情况下对资源执行GET请求。

代码语言:javascript
复制
$ curl localhost:3000/users

应用程序应响应401 Unauthorized状态代码和以下响应正文:

代码语言:javascript
复制
"statusCode": 401,
"error": "Unauthorized"

如果您事先创建了有效令牌并将其与HTTP请求一起传递,则应用程序将分别标识用户,将其对象附加到请求,并允许进一步的请求处理。

代码语言:javascript
复制
$ curl localhost:3000/users -H "Authorization: Bearer TOKEN"

一个完整的工作示例,请点击这里

多种策略

通常,您最终会在整个应用程序中重复使用单一策略。但是,在某些情况下,您可能希望针对不同的范围使用不同的策略。在多个策略的情况下,将第二个参数传递给PassportStrategy函数。通常,此参数是策略的名称。

代码语言:javascript
复制
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt')

在上面的例子中,jwt变成了名字JwtStrategy。之后,您可以@AuthGuard('jwt')像以前一样使用。

GraphQL

为了AuthGuard与GraphQL一起使用,您必须扩展内置AuthGuard类和覆盖getRequest()方法。

代码语言:javascript
复制
@Injectable()
export class GqlAuthGuard extends AuthGuard('jwt') {
  getRequest(context: ExecutionContext) {
    const ctx = GqlExecutionContext.create(context);
    return ctx.getContext().req;
  }
}

我们假设req(请求)已作为上下文值的一部分传递。我们必须在模块设置中设置此行为。

代码语言:javascript
复制
GraphQLModule.forRoot({
  context: ({ req }) => ({ req }),
})

现在,上下文价值将具有req财产。

扫码关注腾讯云开发者

领取腾讯云代金券