如何在Google策略中传递动态参数到回调URL?多角色登录方案
带角色的Google登录注册实现方案
一、通过OAuth2 State传递角色(替代动态回调URL)
直接修改回调URL参数不符合OAuth2规范,且Google控制台要求回调URL提前配置,动态修改会导致验证失败。更标准的做法是利用OAuth2的state参数传递角色信息,具体实现如下:
1. 自定义Google认证守卫,传递role到state
创建自定义守卫,在授权请求时将role参数存入state:
// google-auth.guard.ts import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ExecutionContext } from '@nestjs/common'; @Injectable() export class GoogleAuthGuard extends AuthGuard('google') { async canActivate(context: ExecutionContext) { const request = context.switchToHttp().getRequest(); const role = request.query.role; // 将role存入state,随授权请求传递给Google this.setRequestOptions(request, { state: role }); return super.canActivate(context); } private setRequestOptions(request: any, options: any) { request.session = request.session || {}; request.session.passport = request.session.passport || {}; request.session.passport.options = options; } }
2. 修改GoogleStrategy,启用passReqToCallback并获取state中的role
修改策略,允许获取请求对象,并从回调的state参数中提取角色:
// google.strategy.ts import { PassportStrategy } from '@nestjs/passport'; import { Profile, Strategy, VerifyCallback } from 'passport-google-oauth20'; import { Injectable, Request } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; @Injectable() export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { constructor(config: ConfigService) { super({ clientID: config.get('GOOGLE_CLIENT_ID'), clientSecret: config.get('GOOGLE_CLIENT_SECRET'), callbackURL: config.get('GOOGLE_CALLBACK_URL'), // 保持固定配置 scope: ['email', 'profile'], passReqToCallback: true, // 启用后可在validate中获取request对象 }); } async validate( @Request() req: any, accessToken: string, refreshToken: string, profile: Profile, done: VerifyCallback, ): Promise<any> { const { name, emails, photos } = profile; // 从回调的state参数中获取role const role = req.query.state; const user = { email: emails[0].value, firstName: name.givenName, lastName: name.familyName, username: profile.displayName, accessToken, user_id: profile.id, role, // 将角色加入用户数据 }; done(null, user); } }
3. 更新控制器,使用自定义守卫并处理回调
替换默认守卫,让授权请求通过自定义守卫传递role,回调时直接从用户数据中获取角色:
// social-auth.controller.ts import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { SocialAuthService } from './social-auth.service'; import { GoogleAuthGuard } from './google-auth.guard'; import { ApiOkResponse, ApiQuery, ApiTags } from '@nestjs/swagger'; import { Role } from '@prisma/client'; @ApiTags('social-auth') @Controller('social-auth') export class SocialAuthController { constructor(private readonly socialAuthService: SocialAuthService) {} @ApiOkResponse({ description: 'Google login' }) @ApiQuery({ name: 'role', required: true, type: String, enum: ['ADMIN', 'FREELANCER', 'CLIENT'], description: 'role of user', }) @Get('google') @UseGuards(GoogleAuthGuard) // 使用自定义守卫 async googleAuth() { // 守卫自动处理重定向到Google授权页,无需额外逻辑 } @ApiOkResponse({ description: 'Google login redirect' }) @Get('google/redirect') @UseGuards(AuthGuard('google')) googleAuthRedirect(@Req() req: any) { // req.user中已包含role参数,传递给service处理 return this.socialAuthService.googleLogin(req.user); } }
4. 在Service中处理角色逻辑
修改服务方法,根据角色创建或更新用户,同时校验角色合法性:
// social-auth.service.ts import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PrismaService } from '../infra/database/prisma/prisma.service'; import { AuthService } from '../auth/auth.service'; import { Role } from '@prisma/client'; @Injectable() export class SocialAuthService { constructor( private readonly prisma: PrismaService, private readonly authService: AuthService, ) {} async googleLogin(user: any) { const { email, firstName, lastName, username, user_id, role } = user; // 校验角色合法性,防止恶意参数 const validRoles: Role[] = ['ADMIN', 'FREELANCER', 'CLIENT']; if (!validRoles.includes(role as Role)) { throw new UnauthorizedException('Invalid role'); } // 查找或创建用户 let existingUser = await this.prisma.user.findUnique({ where: { email } }); if (!existingUser) { existingUser = await this.prisma.user.create({ data: { email, firstName, lastName, username, googleId: user_id, role: role as Role, }, }); } else { // 可选:根据业务需求决定是否允许更新已有用户的角色 existingUser = await this.prisma.user.update({ where: { email }, data: { role: role as Role }, }); } // 生成JWT返回 return this.authService.generateToken(existingUser); } }
二、更优实现的注意事项
- Session支持:需确保应用启用Session(如使用
@nestjs/express-session),否则无法传递state参数。 - 角色校验:必须在服务层校验角色合法性,避免权限漏洞。
- Google控制台配置:仅需配置固定的回调URL
/social-auth/google/redirect,无需添加role参数。 - 角色更新策略:根据业务需求决定是否允许已有用户修改角色,比如仅允许首次注册时设置角色。
内容的提问来源于stack exchange,提问作者Midyanisa Yuniar
相关产品推荐
相关产品推荐

