nestjs微信小程序登录授权
前言
nestjs官方文档是英文,太难搞了,摸索了两天 ,把经验记下来。 以后备用
目录结构
|--src //项目根目录
|--modules // 模块 比如用户模块,商品模块
|--app //入口模块
|--utils //一些工具类,比如生成token
|--decorator //自定义注解
|--guard // 自定义守卫
|--constants.ts //常量
|--main.ts //启动入口
登录
小程序前端传入js_code,后端拿这个请求微信服务端,获得用户信息(openid, session_key)
小程序登录页面的js
async onLogin() {
const { code } = await wx.login();
const url = 'http://localhost:3000/login';
const {data} = await request({method:"POST", url, data:{code} });
// 登录成功之后 将token存入全局 并重定向到首页
wx.setStorageSync('token', data.token);
wx.redirectTo({url: '/pages/index/index'})
}
后端controller 登录成功之后 将token返回给前端 让其存储起来 以后每次请求都要携带 作为身份识别
@Post('login')
async onLogin(@Body('code') js_code: string): Promise<any> {
const token = await this.utils.genToken(js_code);
return { token };
}
后端 utils.ts 里genToken代码
import { JwtService } from '@nestjs/jwt';
import { HttpService } from '@nestjs/axios';
import { firstValueFrom, map } from 'rxjs';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
@Injectable()
export default class Utils {
constructor(
private readonly httpService: HttpService,
private readonly jwtService: JwtService,
) {}
async genToken(js_code: string) {
// openid是用户在同一个小程序下的唯一表示,
// 即同一个用户在不同的小程序下的openid是不同的
// 所以当你的appid变化之后,用户的openid就会发生变化,只变AppSecret时 openid是不会变的。
const params: any = {
appid: 'xxxxxx', // 管理员在微信公众平台获取
secret: 'xxxxxx', // 管理员在微信公众平台获取
grant_type: 'authorization_code', // 写死
js_code,
};
// 请求微信服务端接口 返回session_key和openid
const res = await firstValueFrom(
this.httpService
.get('https://api.weixin.qq.com/sns/jscode2session', { params })
.pipe(map((response) => response.data)),
);
const { openid, session_key, errcode } = res;
// 如果微信服务端抛出错误,则将错误直接返回给前端
if (errcode) {
// https://betheme.net/news/txtlist_i90049v.html?action=onClick
throw new HttpException({ ...res }, HttpStatus.INTERNAL_SERVER_ERROR);
} else {
// 根据session_key和openid 组合成一个用户登录唯一标识token 并维护其生命周期(比如会话变更 就需要重新让其登录)
// 之后的小程序端的每一个请求都需要携带此token 让我鉴权
return this.jwtService.sign({ openid, session_key });
}
}
}
获取用户信息
接上步, 如果小程序登录成功 应该跳转到首页。 首页一般都会调用一些接口 比如获取用户信息啥的 这里举例获得用户的openid
后端controller
@Post('userinfo')
userInfo(@AuthUser() user) {
console.log(user);
return { data: {} };
}
@AuthUser 是自定义的一个注解,用来获取请求参数中的user的,在decorator/index.ts
里
当然你也可以不用注解 而通过接受@Request 里获取,只是麻烦些而已。
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const AuthUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
为啥请求参数里有个user呢 这是因为 我将user注入到全局所有的request里了 通过守卫guard/auth.guard.ts
。
接下来讲讲这个守卫是干什么。
守卫,相当于拦截器,比拦截器写起来体验更好。
用来鉴权的,定义那些接口需要token 哪些不需要token 一般处理登录登出接口 其它都需要
并在这里边 将token解码 将解码后的内容塞入request 以便后续的controller 们使用
import {
Injectable,
Inject,
CanActivate,
HttpException,
HttpStatus,
ExecutionContext,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthGuard implements CanActivate {
@Inject()
private readonly jwtService: JwtService;
// context 请求的(Response/Request)的引用
async canActivate(context: ExecutionContext): Promise<boolean> {
// console.log('进入全局权限守卫...');
// 获取请求对象
const request = context.switchToHttp().getRequest();
// 获取请求头中的token字段
const token = context.switchToRpc().getData().headers.token;
// 如果白名单内的路由就不拦截直接通过
if (this.hasUrl(this.urlList, request.url)) {
return true;
}
// 验证token的合理性以及根据token做出相应的操作
if (token) {
try {
// 这里可以添加验证逻辑
const payload = this.jwtService.verify(token);
request.user = payload; // 注入到请求中 这样每个控制器直接拿到用户信息 不用每个都需要去解码了
return true;
} catch (e) {
throw new HttpException(
'没有授权访问,请先登录',
HttpStatus.UNAUTHORIZED,
);
}
} else {
throw new HttpException('没有授权访问,请先登录', HttpStatus.UNAUTHORIZED);
}
}
// 白名单数组
private urlList: string[] = ['/login'];
// 验证该次请求是否为白名单内的路由
private hasUrl(urlList: string[], url: string): boolean {
let flag: boolean = false;
if (urlList.indexOf(url) >= 0) {
flag = true;
}
return flag;
}
}
最后注意语法
app.module.ts 里需要提前注入这些依赖 方可在上遍中 使用一些服务和工具
import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { JwtModule } from '@nestjs/jwt';
import Utils from '../../utils';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from 'src/guard/auth.guard';
@Module({
imports: [
HttpModule,
JwtModule.register({ // 注入jwt模块
secret: 'dsh',
signOptions: { expiresIn: '60s' },
}),
],
controllers: [AppController],
providers: [
AppService,
Utils,
{ // 注将自定义守卫入全局
provide: APP_GUARD,
useClass: AuthGuard,
},
],
})
export class AppModule {}