Nest.js中Passport Google与Apple认证冲突报错求助
问题:同时使用passport-google-oauth20和passport-appleid时Google认证报错
错误信息
[Nest] 987 - 07/22/2022, 9:49:18 AM ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'callbackURL') TypeError: Cannot read properties of undefined (reading 'callbackURL') at GoogleStrategy.OAuth2Strategy.authenticate (/home/node/app/node_modules/passport-appleid/lib/strategy.js:76:59) at attempt (/home/node/app/node_modules/passport/lib/middleware/authenticate.js:369:16) at authenticate (/home/node/app/node_modules/passport/lib/middleware/authenticate.js:370:7) at /home/node/app/node_modules/@nestjs/passport/dist/auth.guard.js:96:3 at new Promise (<anonymous>) at /home/node/app/node_modules/@nestjs/passport/dist/auth.guard.js:88:83 at MixinAuthGuard.<anonymous> (/home/node/app/node_modules/@nestjs/passport/dist/auth.guard.js:49:36) at Generator.next (<anonymous>) at fulfilled (/home/node/app/node_modules/@nestjs/passport/dist/auth.guard.js:17:58) at processTicksAndRejections (node:internal/process/task_queues:96:5)
问题描述
用passport-google-oauth20和passport-appleid分别实现Google与Apple认证功能,单独使用任意一种策略均正常,但同时启用时,调用GET google-sign-in接口会触发上述错误。注释掉UserModule中的AppleStrategy后,Google认证恢复正常。无法理解为何GoogleStrategy的OAuth2Strategy.authenticate会调用passport-appleid包内的逻辑。
相关代码
控制器代码
@UseGuards(AuthGuard('google')) @ApiOperation({ summary: 'google sign in' }) @Get('google-sign-in') public async signInWithGoogle(@Req() req: any) { return req.user; } @UseGuards(AuthGuard('google')) @ApiOperation({ summary: 'google redirect' }) @Get('auth/google/redirect') public async signInWithGoogleRedirect(@Req() req: any) { return this.userService.socialSignIn(req.user); } @UseGuards(AuthGuard('apple')) @ApiOperation({ summary: 'apple sign in' }) @Get('apple-sign-in') public async signInWithApple(@Req() req: any) { return req.user; } @UseGuards(AuthGuard('apple')) @ApiOperation({ summary: 'apple redirect' }) @Get('apple/redirect') public async signInWithAppleRedirect(@Req() req: any) { return this.userService.socialSignIn(req.user); }
GoogleStrategy代码
import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Profile } from 'passport'; import { Strategy, VerifyCallback } from 'passport-google-oauth20'; import { RegisteredPlatformsEnum } from 'src/_enum/registered-platforms.enum'; import { ConfigService } from '../config.services'; @Injectable() export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { constructor(configService: ConfigService) { super({ clientID: configService.get('GOOGLE_AUTH_CLIENT_ID'), clientSecret: configService.get('GOOGLE_AUTH_CLIENT_SECRET'), callbackURL: configService.get('GOOGLE_AUTH_CALLBACK_URL'), scope: ['email', 'profile'], }); } public async validate( accessToken: string, refreshToken: string, profile: Profile, done: VerifyCallback, ): Promise<any> { const { name, emails } = profile; const [userEmail] = emails; const user = { email: userEmail.value, name: name.givenName, registeredPlatform: RegisteredPlatformsEnum.GOOGLE, accessToken, }; done(null, user); } }
AppleStrategy代码
import path from 'path'; import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { Profile } from 'passport'; import { Strategy, VerifyCallback } from 'passport-appleid'; import { RegisteredPlatformsEnum } from 'src/_enum/registered-platforms.enum'; import { ConfigService } from '../config.services'; @Injectable() export class AppleStrategy extends PassportStrategy(Strategy, 'apple') { constructor(configService: ConfigService) { super({ clientID: configService.get('APPLE_AUTH_CLIENT_ID'), callbackURL: configService.get('APPLE_AUTH_CALLBACK_URL'), teamId: configService.get('APPLE_AUTH_TEAM_ID'), keyIdentifier: configService.get('APPLE_AUTH_KEY_IDENTIFIER'), privateKeyPath: path.join( __dirname, `./AuthKey_${configService.get('APPLE_AUTH_KEY_IDENTIFIER')}.p8`, ), }); } public async validate( accessToken: string, refreshToken: string, profile: Profile, done: VerifyCallback, ): Promise<any> { const { name, emails } = profile; const [userEmail] = emails; const user = { email: userEmail.value, name: name.givenName, registeredPlatform: RegisteredPlatformsEnum.APPLE, accessToken, }; done(null, user); } }
UserModule代码
import { Module, forwardRef, Logger } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AppModule } from '../../app.module'; import { ConfigService } from '../../services/config.services'; import { UserService } from './services/users.service'; import { UserController } from './user.controller'; import { UserEntity } from '../../model/user.entity'; import { EmailService } from '../../services/email.service'; import { UserRepository } from 'src/repositories/user.repository'; import { GoogleStrategy } from 'src/services/strategy/google-strategy'; import { AppleStrategy } from 'src/services/strategy/apple-strategy'; @Module({ imports: [ forwardRef(() => AppModule), TypeOrmModule.forFeature([UserEntity, UserRepository]), ], controllers: [UserController], providers: [ Logger, UserService, ConfigService, EmailService, // passport strategies AppleStrategy, GoogleStrategy, ], exports: [UserService], }) export class UserModule {}
解决方法
问题根源是passport-appleid包存在原型污染:它直接修改了passport-oauth2中OAuth2Strategy的原型方法,导致所有继承该基类的策略(包括GoogleStrategy)都被篡改。
方案1:调整策略加载顺序
在UserModule的providers中,将GoogleStrategy放在AppleStrategy前面,让GoogleStrategy先完成原型方法的绑定,避免被后续加载的AppleStrategy覆盖:
providers: [ Logger, UserService, ConfigService, EmailService, // 先加载GoogleStrategy GoogleStrategy, AppleStrategy, ],
方案2:更换无原型污染的Apple认证包
改用@node-passport/apple这类不修改基类原型的包,替代passport-appleid。
方案3:修复passport-appleid的原型污染问题
如果不想换包,可手动修改passport-appleid/lib/strategy.js代码,将直接修改原型的逻辑改为类继承:
// 原代码(污染原型) const OAuth2Strategy = require('passport-oauth2').Strategy; OAuth2Strategy.prototype.authenticate = function() { /* 自定义逻辑 */ } // 修改为类继承(不污染原型) const OAuth2Strategy = require('passport-oauth2').Strategy; class AppleStrategy extends OAuth2Strategy { constructor(options, verify) { super(options, verify); this.name = 'apple'; } authenticate(req, options) { // 自定义逻辑 super.authenticate(req, options); } } module.exports = AppleStrategy;
内容的提问来源于stack exchange,提问作者Orest Mirko
相关产品推荐
相关产品推荐

