nestjs的jwt认证利用了 Passport.js 的认证机制。要根据这个源码实现您自己的 AuthGuard,需要理解几个关键部分:如何集成 Passport.js、如何处理认证结果,以及如何使用 NestJS 的依赖注入系统。
先自定义一个策略函数类
// wsy.strategy.ts
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class WsyStrategy extends PassportStrategy(Strategy) {
constructor() {
// 由于是indectable,所以实例化的时候就会执行super,super是PassportStrategy(Strategy)返回的类
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: '123456',
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
PassportStrategy
函数返回的是MixinStrategy
类,该类提供callback
去调用我们自定义的validate
方法
// @nestjs\passport\dist\passport\passport.strategy.js
"use strict";
function PassportStrategy(Strategy, name, callbackArity) {
class MixinStrategy extends Strategy {
constructor(...args) {
// 此时该回调函数的this是指向WsyStrategy的
const callback = async (...params) => {
const done = params[params.length - 1];
try {
// 调用WsyStrategy中的validate方法
const validateResult = await this.validate(...params);
if (Array.isArray(validateResult)) {
done(null, ...validateResult);
}
else {
done(null, validateResult);
}
}
catch (err) {
done(err, null);
}
};
...
// 调用Strategy的constructor方法,并传入callback
super(...args, callback);
// 获取唯一的passport为Authenticator
const passportInstance = this.getPassportInstance();
if (name) {
// 关键!!这里调用Authenticator的use方法设置当前的strategy为WsyStrategy
passportInstance.use(name, this);
}
else {
passportInstance.use(this);
}
}
getPassportInstance() {
return passport;
}
}
return MixinStrategy;
}
保存我们的WsyStrategy
策略
// passport\lib\authenticator.js
Authenticator.prototype.use = function(name, strategy) {
if (!strategy) {
strategy = name;
name = strategy.name;
}
if (!name) { throw new Error('Authentication strategies must have a name'); }
this._strategies[name] = strategy;
return this;
};
保存PassportStrategy
的callback
,后面解析jwt
后调用
// passport-jwt\lib\strategy.js
function JwtStrategy(options, verify) {
passport.Strategy.call(this);
this.name = 'jwt';
...
// 保存了PassportStrategy的callback!!
this._verify = verify;
...
}
此时发起请求前的配置以及配置好了,还有发起请求后的配置:
// auth.controller.ts
import { Controller, Post, Body, HttpCode, HttpStatus, UseGuards, Get, Request } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './login.dto'; // 假设您有一个 DTO 定义登录请求的结构
import { AuthGuard } from '@nestjs/passport';
@Controller('auth')
export class AuthController {
// 首先请求被AuthGuard的返回给拦截,执行AuthGuard得到的是MixinAuthGuard
@UseGuards(AuthGuard('jwt'))
@Get()
getProtectedRoute(@Request() req) {
// 如果 JWT 验证通过,req.user 将包含用户信息
return req.user;
}
}
进入AuthGuard
的编译后的源码
// AuthGuard是createAuthGuard
export const AuthGuard: (type?: string | string[]) => Type<IAuthGuard> =
memoize(createAuthGuard);
function createAuthGuard(type?: string | string[]): Type<IAuthGuard> {
class MixinAuthGuard<TUser = any> implements CanActivate {
...
async canActivate(context: ExecutionContext): Promise<boolean> {
const options = {
...defaultOptions,
...this.options,
...(await this.getAuthenticateOptions(context))
};
const [request, response] = [
this.getRequest(context),
this.getResponse(context)
];
const passportFn = createPassportContext(request, response);
// 最终是执行passportFn=createPassportContext
const user = await passportFn(
type || this.options.defaultStrategy,
options,
(err, user, info, status) =>
this.handleRequest(err, user, info, context, status)
);
request[options.property || defaultOptions.property] = user;
return true;
}
handleRequest(err, user, info, context, status): TUser {
if (err || !user) {
throw err || new UnauthorizedException();
}
return user;
}
}
}
返回一个函数,该函数返回一个promise
const createPassportContext = (request, response) => (type, options, callback) => new Promise((resolve, reject) => passport.authenticate(type, options, (err, user, info, status) => {
try {
request.authInfo = info;
return resolve(callback(err, user, info, status));
}
catch (err) {
reject(err);
}
})(request, response, (err) => (err ? reject(err) : resolve())));
化简后
const createPassportContext = (request, response) => (type, options, callback) => {
// 该函数返回一个 Promise
return new Promise((resolve, reject) => {
// 使用 Passport 的 authenticate 方法进行认证
passport.authenticate(type, options, (err, user, info, status) => {
try {
// 在认证回调中,将认证的信息存储在请求对象的 authInfo 属性中
request.authInfo = info;
// 调用传入的回调函数,传递认证结果和信息
return resolve(callback(err, user, info, status));
} catch (err) {
// 捕获可能的错误并将其拒绝
reject(err);
}
})(request, response, (err) => {
// 处理认证过程中的错误
if (err) {
reject(err); // 如果有错误,拒绝 Promise
} else {
resolve(); // 没有错误,解析 Promise
}
});
});
};
主要执行passport.authenticate
方法
Authenticator.prototype.authenticate = function(strategy, options, callback) {
return this._framework.authenticate(this, strategy, options, callback);
};
module.exports = function authenticate(passport, name, options, callback) {
...
return function authenticate(req, res, next) {
...
(function attempt(i) {
var layer = name[i];
// If no more strategies exist in the chain, authentication has failed.
if (!layer) { return allFailed(); }
// Get the strategy, which will be used as prototype from which to create
// a new instance. Action functions will then be bound to the strategy
// within the context of the HTTP request/response pair.
var strategy, prototype;
if (typeof layer.authenticate == 'function') {
strategy = layer;
} else {
// 关键!!这里执行_strategy方法并传入layer('jwt')去获取(初始化时调用Authenticator的use方法设置当前的strategy为WsyStrategy)WsyStrategy
prototype = passport._strategy(layer);
if (!prototype) { return next(new Error('Unknown authentication strategy "' + layer + '"')); }
strategy = Object.create(prototype);
}
...
// 执行WsyStrategy继承的JwtStrategy的authenticate方法
strategy.authenticate(req, options);
})(0); // attempt
};
};
passport._strategy(layer)
方法:
Authenticator.prototype._strategy = function(name) {
return this._strategies[name];
};
函数strategy.authenticate(req, options)
最终通过jsonwebtoken库解码我们的authtoken并生成下图所示:
然后把生成的结果传给PassportStrategy
的callback
函数,该函数会调用我们定义好的validate
方法。如果validate
方法没有返回值或者抛出异常那么就会判断为失败。validate
返回值最后会被传入auth.controller.ts
的getProtectedRoute
的req
方法。
还有@InjectRepository(User)
中是做了哪些事情呢?InjectRepository
只不过在user
后面加了一个repository
名字userRepository
传给了Inject
,相当于@Inject('UserRepository')
。
// users.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import * as bcrypt from 'bcrypt';
@Injectable()
export class UsersService {
constructor(
// InjectRepository只不过在user后面加了一个repository名字userRepository传给了Inject,相当于@Inject('UserRepository')
// @Inject('UserRepository')是Reflect.defineMetadata(constants_1.SELF_DECLARED_DEPS_METADATA, dependencies, target),给当前类设置依赖{index: 0,param: "UserRepository"},类型为'self:paramtypes'
@InjectRepository(User)
private usersRepository: Repository<User>,
) {
console.log(123);
}
async create(username: string, password: string): Promise<User> {
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = this.usersRepository.create({ username, password: hashedPassword });
return this.usersRepository.save(newUser);
}
async validateUser(username: string, pass: string): Promise<any> {
const user = await this.usersRepository.findOne({ where: { username } });
if (user && await bcrypt.compare(pass, user.password)) {
const { password, ...result } = user;
return result;
}
return null;
}
}
那么有了注入,那么就有提供注入的地方,这里只有TypeOrmModule.forFeature([User])
,我们看看forFeature
代码。
// users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { User } from './user.entity';
import { UsersController } from './users.controller ';
import { JwtModule } from '@nestjs/jwt';
@Module({
// TypeOrmModule.forFeature([User])设置provider,为了注入repostory,比如@Inject('UserRepository')。这个方法会生成UserRepository的provider
imports: [TypeOrmModule.forFeature([User]),JwtModule.register({
secret: '123456', // 设置一个秘密的密钥
signOptions: { expiresIn: '3600s' }, // 设置 token 过期时间
}),],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersLoginModule {}
在createTypeOrmProviders(entities, dataSource)
中返回一个provider
,对应@Inject("UserRepository")
@Module({})
export class TypeOrmModule {
static forRoot(options?: TypeOrmModuleOptions): DynamicModule {
return {
module: TypeOrmModule,
imports: [TypeOrmCoreModule.forRoot(options)],
};
}
static forFeature(
entities: EntityClassOrSchema[] = [],
dataSource:
| DataSource
| DataSourceOptions
| string = DEFAULT_DATA_SOURCE_NAME,
): DynamicModule {
const providers = createTypeOrmProviders(entities, dataSource);
EntitiesMetadataStorage.addEntitiesByDataSource(dataSource, [...entities]);
return {
module: TypeOrmModule,
providers: providers,
exports: providers,
};
}
static forRootAsync(options: TypeOrmModuleAsyncOptions): DynamicModule {
return {
module: TypeOrmModule,
imports: [TypeOrmCoreModule.forRootAsync(options)],
};
}
}
这里返回一个provider
。
function createTypeOrmProviders(entities, dataSource) {
return (entities || []).map((entity) => ({
provide: (0, typeorm_utils_1.getRepositoryToken)(entity, dataSource),// 生成标识符UserRepository,entiry.name+Repository
useFactory: (dataSource) => {// 重要!!3.将entity设置到repository上
const enitityMetadata = dataSource.entityMetadatas.find((meta) => meta.target === entity);
const isTreeEntity = typeof enitityMetadata?.treeType !== 'undefined';
return isTreeEntity
? dataSource.getTreeRepository(entity)
: dataSource.options.type === 'mongodb'
? dataSource.getMongoRepository(entity)
: dataSource.getRepository(entity);
},
inject: [(0, typeorm_utils_1.getDataSourceToken)(dataSource)],// 一个数组,指定 useFactory 函数的依赖项
/**
* 用于解决动态模块序列化问题的额外属性。它通过 getMetadataArgsStorage 函数找到与实体类相对应的表元数据
* that occurs when "TypeOrm#forFeature()" method is called with the same number
* of arguments and all entities share the same class names.
*/
targetEntitySchema: (0, typeorm_1.getMetadataArgsStorage)().tables.find((item) => item.target === entity),
}));
}
理解代码的关键部分
-
Passport
集成:
代码中使用了Passport.js
的passport.authenticate
方法。这是Passport.js
的核心功能,用于执行认证流程。 - 处理认证结果:
handleRequest
方法处理passport.authenticate
的结果。这里可以自定义决定如何处理认证失败(例如抛出异常)或成功(例如返回用户对象)。
所以我们可以总结下这几个关键模块的功能:
-
wsy.strategy.ts
该模块是给
passport
设置strategy
为WsyStrategy
和传入一些初始化参数,比如密钥,jwt
的获取方式等,以及提供验证函数供用户给出返回值。文章来源:https://www.toymoban.com/news/detail-798153.html -
AuthGuard('jwt')
解析header中的token
并将validate
函数的返回值设置到req
上,默认是user
,也可以自己设置。文章来源地址https://www.toymoban.com/news/detail-798153.html
到了这里,关于nestjs之JWT认证实现流程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!