跳到主要内容

passport 实现 Google 三方账号登录

上节我们实现了 Github 登录,这节继续来实现下 Google 登录。

创建个 nest 项目:

nest new google-login

进入项目,安装 passport 的包:

npm install --save passport @nestjs/passport

然后安装 google 的策略包。

这个可以去 passport 的网站搜索:

找下载量最多的那个。

然后安装下:

npm install --save passport-google-oauth20
npm install --save-dev @types/passport-google-oauth20

我们先做 google 登录,很明显,最关键的也是要获取 client id 和 client secret。

打开 google cloud 的控制台页面https://console.cloud.google.com/welcome

点击左上角的按钮,然后点击 new project:

填入项目名后点击 create:

点击左上角的按钮切换到你刚刚创建的 project:

进入 api & service 页面:

点击 OAuth consent screen,然后勾选 external,点击 create:

输入三个必填信息:

点击 save and continue。

然后点击 Credentials 创建凭证:

输入应用类型、name、填入授权的域名、回调的 url,点击 create:

这样 client id 和 client secret 就生成好了:

接下来写代码:

nest g module auth

生成 auth 模块,然后创建 auth/google.strategy.ts

import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy } from "passport-google-oauth20";

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, "google") {
constructor() {
super({
clientID:
"122695705559-9nr9alq0s53e2pr3vkiv2h7vau917ic4.apps.googleusercontent.com",
clientSecret: "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",
callbackURL: "http://localhost:3000/callback/google",
scope: ["email", "profile"],
});
}

validate(accessToken: string, refreshToken: string, profile: any) {
const { name, emails, photos } = profile;
const user = {
email: emails[0].value,
firstName: name.givenName,
lastName: name.familyName,
picture: photos[0].value,
accessToken,
};
return user;
}
}

这里填入刚刚的 clientID、clientSecret、callbackURL。

然后在 AuthModule 引入:

import { Module } from "@nestjs/common";
import { GoogleStrategy } from "./google.strategy";

@Module({
providers: [GoogleStrategy],
})
export class AuthModule {}

之后在 AppController 添加两个路由:

import { Controller, Get, Req, UseGuards } from "@nestjs/common";
import { AppService } from "./app.service";
import { AuthGuard } from "@nestjs/passport";

@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}

@Get()
getHello(): string {
return this.appService.getHello();
}

@Get("google")
@UseGuards(AuthGuard("google"))
async googleAuth() {}

@Get("callback/google")
@UseGuards(AuthGuard("google"))
googleAuthRedirect(@Req() req) {
if (!req.user) {
return "No user from google";
}

return {
message: "User information from google",
user: req.user,
};
}
}

一个是登录的,一个是回调的。

把服务跑起来:

npm run start:dev

测试下:

可以看到,google 的用户信息拿到了:

这里没有 github 返回的那种有 id,但这里返回了 email,同样可以唯一标识用户。

你可以试下 medium.com 的三方登录:

用 google 账号登录之后,会让你完善一些信息,然后 create count。

也就是基于你 google 账号里的东西,再让你填一些东西之后,完成账号注册。

之后你 google 登录,就会查到这个账号,从而直接登录,不用输密码。

或者 hub.docker.com 的三方登录:

也是在 github 账号登录后,让你填一些其余信息,完成注册。

之后三方账号授权后,直接登录。

我们也来实现下:

引入下 TypeORM 来操作数据库:

npm install --save @nestjs/typeorm typeorm mysql2

AppModule 里引入 TypeOrmModule:

import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { AuthModule } from "./auth/auth.module";
import { TypeOrmModule } from "@nestjs/typeorm";

@Module({
imports: [
AuthModule,
TypeOrmModule.forRoot({
type: "mysql",
host: "localhost",
port: 3306,
username: "root",
password: "guang",
database: "google-login",
synchronize: true,
logging: true,
entities: [],
poolSize: 10,
connectorPackage: "mysql2",
extra: {
authPlugin: "sha256_password",
},
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

在 mysql workbench 创建这个 database:

添加 src/user.entity.ts

import {
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from "typeorm";

export enum RegisterType {
normal = 1,
google = 2,
}
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;

@Column({
length: 50,
})
email: string;

@Column({
length: 20,
})
password: string;

@Column({
comment: "昵称",
length: 50,
})
nickName: string;

@Column({
comment: "头像 url",
length: 200,
})
avater: string;

@Column({
comment: "注册类型: 1.用户名密码注册 2. google自动注册",
default: 1,
})
registerType: RegisterType;

@CreateDateColumn()
createTime: Date;

@UpdateDateColumn()
updateTime: Date;
}

有 id、email、nickName、avater、registerType、createTime、updateTime 7 个字段。

registerType 用来标识哪种注册方式,正常注册是 1,google 账号自动注册是 2。

这里要区分是因为 google 方式注册就不用 password 了,验证逻辑不一样。

在 entities 里引入:

跑一下试试:

npm run start:dev

这部分和我们单独跑 typeorm 没啥区别:

然后是增删改查,我们可以注入 EntityManager:

自动创建了对应的表。

在 mysql workbench 里也可以看到:

然后在 AppService 里注入 EntityManager 来操作 user 表:

import { Injectable } from "@nestjs/common";
import { InjectEntityManager } from "@nestjs/typeorm";
import { EntityManager } from "typeorm";
import { User } from "./user.entity";

export interface GoogleInfo {
email: string;
firstName: string;
lastName: string;
picture: string;
}

@Injectable()
export class AppService {
@InjectEntityManager()
entityManager: EntityManager;

getHello(): string {
return "Hello World!";
}

async registerByGoogleInfo(info: GoogleInfo) {
const user = new User();

user.nickName = `${info.firstName}_${info.lastName}`;
user.avater = info.picture;
user.email = info.email;
user.password = "";
user.registerType = 2;

return this.entityManager.save(User, user);
}

async findGoogleUserByEmail(email: string) {
return this.entityManager.findOneBy(User, {
registerType: 2,
email,
});
}
}

实现了 findGoogleUserByEmail 方法,可以根据 email 查询 google 注册的账号。

实现了 registerByGoogleInfo 方法,根据 google 返回的信息自动注册账号。

然后在 AppController 里改下 callback 的逻辑:

@Get('callback/google')
@UseGuards(AuthGuard('google'))
async googleAuthRedirect(@Req() req) {
const user = await this.appService.findGoogleUserByEmail(req.user.email);

if(!user) {
const newUser = this.appService.registerByGoogleInfo(req.user);
return newUser;
} else {
return user;
}
}

首先根据 email 查询 google 方式登录的 user,如果有,就自动登录。

否则自动注册然后登录。

这里因为 google 返回的信息是全的,就直接自动注册了。

如果不全,需要再跳转一个页面填写其余信息之后再自动注册。

测试下:

因为前面登录过 google 账号并授权了,短时间内不需要再次授权,所以这里直接触发了注册并登录了。

当你用这个 google 账号登录,就会直接登录,不需要再注册了。

当然,网站登录后一般都会重定向到首页,那这时候怎么返回 jwt 的token 呢?

看下 https://hub.docker.com 怎么做的:

可以看到,它并不是直接返回 jwt 的 token,而是重定向回首页,在 cookie 里携带 token。

这样前端只要判断下如果 cookie 里有这些 token 就自动登录就好了。

这就是三方账号登录的实现原理。

案例代码上传了小册仓库

总结

我们实现了基于 google 的三方账号登录。

首先搜索对应的 passport 策略,然后生成 client id 和 client secret。

在 nest 项目里使用这个策略,添加登录和 callback 的路由。

之后基于 google 返回的信息来自动注册,如果信息不够,可以重定向到一个 url 让用户填写其余信息。

之后再次用这个 google 账号登录的话,就会自动登录。

现在,你可以在你的应用中加上 docker.com 这种三方账号登录了: