You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 16:24:59