前言
无
一、认证和鉴权
认证和鉴权是后端系统很常见的功能了,这里主要讲一下认证的实现和常见策略(userinfo和jwt认证)
认证(Authentication)
:主要是对用户信息的一个认证,比如用户登录行为,密码/token验证没问题,则认证用户登录成功鉴权(Authorization)
:一般在认证之后,对用户的权限进行鉴定,允许权限内可允许的操作;比如校园系统里,学生可以选课,老师可以进行设置课程时间等;
本文章主要讲认证
的部分;
二、nestjs中的认证(Strategy)
生活中认证的方式有很多种,比如你在跟别人打电话过程中,别人要确定你是不是xxx,而不是骗子,那你有几种方式可以认证
- 身份证号码
- 学校名称、所在班级、学号
- 公司信息、职位待遇
- 身体特征等
以上特征,如果对方匹配无误,就可以完成一次简单的认证,选择其中一种方式认证,也称作“策略(Strategy)”
对应nestjs中的认证,也是对应不同的认证策略,举比较常见的认证策略
- local策略(账号/密码匹配)
- token策略(生成的jwt没过期)
简单的流程图
graph LR
前端请求 --> nestjs服务;
--> 对应的策略guards守卫 --> |YES| yse;
graph TD
前端请求 --> nestjs服务 --> 对应路由 --> B{对应策略guard守卫} --> 通过 --> 返回业务信息;
B --> 策略失败 --> 返回401未授权
三、账号/密码策略实现
首先快速创建项目和代码
nest new nest-auth
cd nest-auth
yarn run start:dev
# 创建auth和users目录
nest g module auth
nest g service auth
nest g module users
nest g service users
执行完毕后,项目会出现auth
和users
目录以及里面的代码,auth对应存储我们实现认证的代码,users对应用户信息的逻辑;正常来说,用户信息会从数据库拿,这里为了demo简单,会写死在代码里面;
然后我们完善一个auth和users一些基础代码的编写:获取用户
、校验用户
,比较简单,直接贴代码
auth.module.ts
文件
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from 'src/users/users.module';
@Module({
imports: [UsersModule],
providers: [AuthService],
exports: [AuthService],
})
export class AuthModule {}
auth.service.ts
文件,主要实现了validateUser
方法进行用户校验,依赖了userService
import { Injectable } from '@nestjs/common';
import { UsersService } from 'src/users/users.service';
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
) {}
async validateUser(username: string, pass: string) {
const user = await this.usersService.findOne(username);
if (user && user.password === pass) {
const { password, ...result } = user;
return result;
}
return null;
}
}
users.module.ts
文件
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
@Module({
providers: [UsersService],
// 因为Auth模块需要到,所以要exports出去
exports: [UsersService],
})
export class UsersModule {}
users.service.ts
文件
import { Injectable } from '@nestjs/common';
export type User = any;
@Injectable()
export class UsersService {
private readonly users = [
{
userId: 1,
username: 'john',
password: 'changeme',
}
];
async findOne(username: string) {
return this.users.find((user) => user.username === username);
}
}
基础部分我们实现了,接下来要引入我们的本地策略
,nestjs对于认证这块专门维护了一个包@nestjs/passport
,另外我们还需要passport-local
来实现本地策略,所以需要先安装依赖
yarn add @nestjs/passport passport-local passport
yarn add @types/passport-local -D
接着在auth文件夹中新建local.strategy.ts
;我们讲一下这段代码,比较重要
- 首先我们继承了PassportStrategy方法,
传进去了passport-local的Strategy
- 如果传错了策略构造函数,会导致不生效
- 其次在构造函数调用了super(),此时super可以传递参数给passport-local的Strategy完成初始化
- 定义了一个validate函数处理认证(nestjs/passport里面就是通过挨个调用策略的validate处理逻辑)
函数参数根据策略不同,这里是username和password
- 如果匹配成功,返回用户信息塞到当前请求对象上的user,如
req.user
,否则返回401
local.strategy.ts
文件
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.service';
// @nestjs/passport和passport是必须的,前者是基于后者在nest中的实现扩展, 后者是依赖
// passport-local是一种具体的验证策略,比如local可以自定义验证用户密码,还有其他的如passport-jwt
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
// usernameField: 'email',
// passwordField: 'password',
// 对应的post请求的时候,参数名称要为email和password
});
}
async validate(username: string, password: string): Promise {
// console.log(username, password);
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
// based on the value we return from the validate() method, and assigns it to the Request object as req.user.
// 返回值会被作为一个对象,设置到req.user上面
return user;
}
}
策略的具体认证部分我们完成了:用户名和密码匹配;路由进来了怎么进行策略认证呢?答案是:
路由守卫;我们在auth里面新建一个local-auth.guard.ts
文件
代码如下,比较简单。需要注意的是!!!AuthGuard('local')
这里的local不是乱填的,而是因为passport-local导出的Strategy的name就是local;
nestjs需要通过name去找到对应的Strategy,完成认证;我们在继承PassportStrategy(Strategy, 'local')
的时候,也可以手动指定name
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}
最后把依赖完善,如下
LocalStrategy
作为providers添加到AuthModule
中- 引入
PassportModule
import { PassportModule } from '@nestjs/passport';
@Module({
imports: [
UsersModule,
PassportModule,
],
providers: [AuthService, LocalStrategy],
exports: [AuthService],
})
把路由添加到需要认证的控制器上,我们在app.controller.ts新建一个路由
app.controller.ts
import { Controller, Post, Get, Req, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { LocalAuthGuard } from './auth/local-auth.guard';
import { Request } from 'express';
import { AuthService } from './auth/auth.service';
@Controller()
export class AppController {
constructor(
private readonly authService: AuthService,
) {}
// @UseGuards(AuthGuard('local'))
// 更加推荐下面这种,前者有魔法字符串,不太好维护
@UseGuards(LocalAuthGuard)
@Post('auth/login')
async login(@Req() req: Request) {
console.log(req.user);
// 认证通过后,req上会有一个user;
return this.authService.login(req.user);
}
}
app模块导入对应的service
路由测试
curl -X POST http://localhost:3000/auth/login -d '{"username": "john", "password": "changeme", "email": "john"}' -H "Content-Type: application/json"
四、jwt策略实现
jwt策略也是类似的,主要是校验用户信息,生成用户token;凭借token完成认证;
首先安装相关依赖
yarn add @nestjs/jwt passport-jwt passport
yarn add @types/passport-jwt -D
在auth里面新建文件
jwt-auth.guard.ts
import { AuthGuard } from '@nestjs/passport';
export class JwtGuard extends AuthGuard('jwt') {}
jwt.strategy.ts
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
// 对应初始化passport-jwt策略的参数
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'anysecret',
});
}
async validate(payload: any) {
// jwt鉴权通过后,会返回鉴权信息,然后将对象设置在req.user上面
return { userId: payload.sub, username: payload.username };
}
}
改一下我们的user.service.ts
,多了一个login方法
async login(user: any) {
const payload = { username: user.username, sub: user.userId };
return {
access_token: this.jwtService.sign(payload),
};
}
引入jwt相关依赖,修改auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from 'src/users/users.module';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: 'anysecret',
signOptions: {
expiresIn: '60s',
},
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}
修改我们的app.controller.ts
,其中login由返回user改成返回token(通过调用authService.login生成),新建profile路由需要token认证;
import { Controller, Post, Get, Req, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { AuthGuard } from '@nestjs/passport';
import { LocalAuthGuard } from './auth/local-auth.guard';
import { Request } from 'express';
import { AuthService } from './auth/auth.service';
import { JwtGuard } from './auth/jwt-auth.guard';
@Controller()
export class AppController {
constructor(
private readonly authService: AuthService,
) {}
@UseGuards(LocalAuthGuard)
@Post('auth/login')
async login(@Req() req: Request) {
// return req.user;
// 修改这里
return this.authService.login(req.user);
}
@UseGuards(JwtGuard)
@Get('profile')
getProfile(@Req() req) {
// 这里可以返回鉴权后的用户信息,也可以做其他操作
return req.user;
}
}
测试流程:进行login ->> 凭借token调用profile获取信息
头部没有携带token或者错误过期token,直接401
五、其他
通过这两个策略,大概可以摸清楚nestjs认证是怎么实现的了,首先nestjs需要找到项目中providers注册的策略,然后完成初始化;
通过路由守卫的方式,找到对应name的Strategy完成认证;
希望可以帮助大家理解,大部分内容来自于nestjs官网
相关资料
- 官网Authentication