nest.js 性能如何(七爪源码使用NestJS)

我第一次使用 OAuth 2.0 时,是在一个需要通过 Google 进行身份验证的项目上,我认为这很简单,因为它是一种已在现代应用程序中大量采用的技术,但它很头疼,因为我没有 '不明白如何保存来自谷歌的数据,我是否应该返回来自提供者的访问令牌,或者我是否应该签署一个终身的 JWT。 所以我想给你详细解释一下。 可能的最简单方法。

nest.js 性能如何(七爪源码使用NestJS)(1)

我遵循的策略我认为不是最好的,实际上没有“最好的策略”,但它对我来说效果很好。

流程如下:

  1. 用户被定向到我们服务器上的端点,并被自动重定向到 Google 登录屏幕。
  2. 放置您的凭据后,您将被重定向到我们服务器的另一个端点,我们将在该端点接收所有用户信息。
  3. 我们将在我们的数据库中查找用户如果用户存在:我们返回一个带有 JSON Web Token 的 Cookie,其中包含用户的 ID 和电子邮件如果用户不存在:我们在我们的数据库中注册用户并返回 cookie
  4. 我们使用 JSON Web Token 来发出经过身份验证的请求

在开始之前,我们必须安装以下软件包,根据您的情况使用 NPM 或 Yarn

npm install @nestjs/jwt @nestjs/passport 护照 护照-google-OAuth2 护照-jwt 护照-本地

npm install -D @types/passport-google-oauth2 @types/passport-jwt @types/passport-jwt @types/passport-local

首先,我们在 Google 开发者控制台中创建一个项目,它在所有提供者中都是类似的。

nest.js 性能如何(七爪源码使用NestJS)(2)

让我们转到 API,然后转到 Credentials 选项卡,Configure Consent Screen

nest.js 性能如何(七爪源码使用NestJS)(3)

nest.js 性能如何(七爪源码使用NestJS)(4)

我们用我们的应用程序的数据填写整个表格

nest.js 性能如何(七爪源码使用NestJS)(5)

我们返回凭据 > 创建凭据 > OAuth 客户端 ID。 选择 Web 应用程序,然后输入名称

nest.js 性能如何(七爪源码使用NestJS)(6)

然后,在 JavaScript 授权域中,我们放置我们服务器的域,在开发模式下它将是 localhost

nest.js 性能如何(七爪源码使用NestJS)(7)

在重定向域中,我们放置用户登录时将被重定向的域,我们通常会有类似 http://localhost:8080/api/auth/google/callback 的内容,与我们启用的每个身份验证提供程序相同 在我们的应用程序中

nest.js 性能如何(七爪源码使用NestJS)(8)

我们点击创建,就是这样! 我们已经完成了提供程序部分,现在我们只需要在我们的服务器上配置所有内容。

我们创建一个名为 auth 的文件夹,我们将在其中存储应用程序的所有身份验证逻辑,在里面,我们将有几个东西,2 个身份验证守护者,一个用于通过 Google 进行身份验证,另一个用于需要 JWT 的请求。

我们将有 2 个 Passport 策略,一个针对每个身份验证提供者的策略,在这种情况下我们只有 Google,另一个从 cookie 中提取 JWT 的策略,这样我们将在每个请求中验证用户的身份,给 或者不访问资源,最后是任何 Nest 模块的基本控制器和服务。

nest.js 性能如何(七爪源码使用NestJS)(9)

Google 的监护人 我们保持简单,我们创建了一个 GoogleOAuthGuard 类,它从 @nest/passport 的 AuthGuard 类扩展而来,标识符为 google

import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class GoogleOauthGuard extends AuthGuard('google') {}

在 Strategies 文件夹中,我们将创建一个文件 google.strategy.ts,这几乎是所有事情发生的地方。

我们创建了一个扩展 PassportStrategy 的 GoogleStrategy 类,该类接收 2 个参数、一个策略和一个策略标识符,我们从 passport-google-oauth2 包中导入策略,并以策略的名称输入“google”

在构造函数中,我们传递了一个带有以下参数的 super:

clientID:我们的客户ID,由google提供

clientSecret:我们的客户端密码,由谷歌提供

callbackURL:我们之前在 Google 控制台中配置的重定向 URL

scopes:一个数组,包含我们想要从用户那里获取的信息,最常见的是 profile 和 email

就我而言,出于安全原因,我将所有信息都放在环境变量中,您也应该这样做。

然后我们创建一个名为 validate() 的函数,它接受 4 个必需参数。

访问令牌

刷新令牌,

profile 和 done 函数,

访问令牌对我们与 Google 服务交互很有用,但在我们的例子中,这不是必需的,我们将只使用 profile,一个包含所有用户信息的对象,以及 done,一个回调函数,我们将在其中传递用户对象并稍后使用将其注册到数据库中并签署 JWT。

您可以记录配置文件对象以查看它带来了什么,每个提供商以不同的方式带来此对象,然后根据您的业务模型和用户模式创建一个包含所需信息的用户对象,最后您返回用户完成的方法。

import { Inject, Injectable } from '@nestjs/common'; import { ConfigType } from '@nestjs/config'; import { PassportStrategy } from '@nestjs/passport'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import config from '../../config/config'; import { User } from '../../users/entities/user.entity'; import { Strategy, VerifyCallback } from 'passport-google-oauth2'; @Injectable() export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { constructor( @Inject(config.KEY) private configService: ConfigType<typeof config>, @InjectRepository(User) private userRepository: Repository<User>, ) { super({ clientID: configService.google.clientID, clientSecret: configService.google.clientSecret, callbackURL: configService.google.callbackURL, scope: ['profile', 'email'], }); } async validate( _accessToken: string, _refreshToken: string, profile: any, done: VerifyCallback, ): Promise<any> { const { id, name, emails, photos } = profile; const user = { provider: 'google', providerId: id, email: emails[0].value, name: `${name.givenName} ${name.familyName}`, picture: photos[0].value, }; done(null, user); } }

在控制器中,我们将创建一个带有“google”端点的 GET 方法,该端点使用之前创建的 google 守卫,该端点会将用户重定向到 Google 登录页面

import { Controller, Get, HttpStatus, Req, UseGuards, } from '@nestjs/common'; import { Response } from 'express'; import { AuthService } from './auth.service'; import { GoogleOauthGuard } from './guards/google-oauth.guard'; @Controller('auth') export class AuthController { constructor(private authService: AuthService) {} @Get('google') @UseGuards(GoogleOauthGuard) // eslint-disable-next-line @typescript-eslint/no-empty-function async auth() {} }

在服务中,我们将有几种方法。

在构造函数中,我们实例化 JWT 服务以生成令牌,并实例化用户服务以注册用户。

generateJwt 方法接收一个有效负载并返回一个签名的 JWT,有效负载可以是用户的 ID、电子邮件或任何我们想要的。

signIn方法接收到一个用户对象,首先通过email查找用户,如果没有找到,说明该用户没有在我们的数据库中注册,我们必须按照我们定义的用户模型注册, 我们在我们的数据库中注册用户并返回一个带有用户 ID 和电子邮件的 JWT。

如果用户存在,我们只需返回 JWT。

import { BadRequestException, Injectable, InternalServerErrorException, } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { generateFromEmail } from 'unique-username-generator'; import { User } from '../users/entities/user.entity'; import { RegisterUserDto } from './dtos/auth.dto'; @Injectable() export class AuthService { constructor( private jwtService: JwtService, @InjectRepository(User) private userRepository: Repository<User>, ) {} generateJwt(payload) { return this.jwtService.sign(payload); } async signIn(user) { if (!user) { throw new BadRequestException('Unauthenticated'); } const userExists = await this.findUserByEmail(user.email); if (!userExists) { return this.registerUser(user); } return this.generateJwt({ sub: userExists.id, email: userExists.email, }); } async registerUser(user: RegisterUserDto) { try { const newUser = this.userRepository.create(user); newUser.username = generateFromEmail(user.email, 5); await this.userRepository.save(newUser); return this.generateJwt({ sub: newUser.id, email: newUser.email, }); } catch { throw new InternalServerErrorException(); } } async findUserByEmail(email) { const user = await this.userRepository.findOne({ email }); if (!user) { return null; } return user; } }

我们在控制器中创建另一个端点,这次指向之前配置的重定向端点。

这个方法会在Request中包含一个用户对象,我们用req.user访问它,我们调用服务中创建的signIn方法,我们传递用户,这个方法返回一个用用户信息签名的JWT,我们返回一个cookie 带有令牌和成功代码。

每次我们想要访问资源时,我们都会为服务器上的每个请求使用该令牌。

import { Controller, Get, HttpStatus, Req, Res, UseGuards, } from '@nestjs/common'; import { Response } from 'express'; import { AuthService } from './auth.service'; import { GoogleOauthGuard } from './guards/google-oauth.guard'; @Controller('auth') export class AuthController { constructor(private authService: AuthService) {} @Get('google') @UseGuards(GoogleOauthGuard) // eslint-disable-next-line @typescript-eslint/no-empty-function async auth() {} @Get('google/callback') @UseGuards(GoogleOauthGuard) async googleAuthCallback(@Req() req, @Res() res: Response) { const token = await this.authService.signIn(req.user); res.cookie('access_token', token, { maxAge: 2592000000, sameSite: true, secure: false, }); return res.status(HttpStatus.OK); } }

一切都快准备好了。 只需创建 JWT 策略来验证在 cookie 中接收到的令牌,并能够创建受保护的路由来访问我们的资源。

我们创建了一个名为 jwt 的 New Guard

import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class JwtAuthGuard extends AuthGuard('jwt') {}

然后,我们创建一个名为 jwt 的新策略,在 super 中我们传递以下参数:

忽略过期:假

secretOrKey:我们的 JWT 秘钥,可以是散列或者任何我们想要的,但必须是保密的。

jwtFromRequest:接收请求对象作为参数并检查 access_token cookie 是否存在的函数,如果存在,则返回它,如果不存在,则使用 passport-jwt 函数从标头中提取令牌。

然后我们创建一个验证 JWT 的方法,我们在数据库中查找用户,为此我们之前实例化了存储库,如果用户不存在,我们返回一个错误,如果存在我们返回我们想要的信息 将在使用 JWT AuthGuard 的每个请求的用户对象中可用

import { ExtractJwt, Strategy } from 'passport-jwt'; import { PassportStrategy } from '@nestjs/passport'; import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'; import { ConfigType } from '@nestjs/config'; import config from '../../config/config'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { User } from '../../users/entities/user.entity'; export type JwtPayload = { sub: string; email: string; }; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { constructor( @Inject(config.KEY) private configService: ConfigType<typeof config>, @InjectRepository(User) private userRepository: Repository<User>, ) { const extractJwtFromCookie = (req) => { let token = null; if (req && req.cookies) { token = req.cookies['access_token']; } return token || ExtractJwt.fromAuthHeaderAsBearerToken()(req); }; super({ ignoreExpiration: false, secretOrKey: configService.jwt.secret, jwtFromRequest: extractJwtFromCookie, }); } async validate(payload: JwtPayload) { const user = await this.userRepository.findOne({ id: payload.sub }); if (!user) throw new UnauthorizedException('Please log in to continue'); return { id: payload.sub, email: payload.email, }; } }

我们已经准备好通过 Google 进行身份验证。 如果要添加更多提供者,则必须遵循相同的步骤并为每个提供者创建策略。

关注七爪网,获取更多APP/小程序/网站源码资源!

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页