上节我们实现了 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: '',
clientSecret: '',
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 mysql2AppModule 里引入 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 这种三方账号登录了:
