官网API点击这里跳转
yarn add @nestjs/passport passport passport-local @types/passport-local @nestjs/jwt passport-jwt @types/passport-jwt --save-dev
// jwt.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
/**
* * 我们遵循了之前描述的所有Passport策略。在我们使用passport-local的用例中,没有配置选项,所以我们的构造函数只是调用super(),没有选项对象。
*/
/**
* 把用户信息存储到user中的类,继承内置类PassportStrategy、重写validate方法
*/
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy, 'encry') { // 继承PassportStrategy方法抛出的类,传递一个Strategy ,第二个参数是自定义的加密的字符串
constructor(private readonly authService: AuthService) { // 依赖注入服务
super(); // 并且调用父类的构造函数
}
public async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password); // (模拟)去数据库 验证是否成功
if (!user) {
throw new UnauthorizedException(); // 抛出未授权异常
}
return user; // validate()方法返回的值自动创建一个对象,并将其分配给Request对象:获取例如:req.user
}
}
validate方法的两个参数,当使用守卫装饰路由后自动在body中注入成形参,然后提供给validate方法内部使用,当验证没有发生错误的情况下,数据在返回的时候被自动注入到request请求对象中,增加了一个user属性,里面包含了返回的数据,类型不限
validate由于继承了内置的类,所以只需要书写守卫装饰器,该方法会被系统自动调用
// auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt'; // 引入jwt
/**
* 验证登录账号密码正确性,并产生token
* @function validateUser 验证登录账号密码正确性
* @function login 产生token
*/
@Injectable()
export class AuthService {
constructor(
// private readonly usersService: UsersService,
private readonly jwtService: JwtService,
) { }
/**
* 模拟数据库匹配账号密码
* @param username 用户名
* @param pass 密码
*/
public async validateUser(username: string, pass: string): Promise<any> {
// 模拟数据
const user = {
id: '12fDAA267CCFa9932c',
username: 'liu',
password: '1111',
};
// 模拟查询数据库,判断密码什么的是否正确
if (user && user.password === pass) {
const { password, ...result } = user;
return result; // 然后返回除了密码的数据,给local.strategy.ts那边使用
}
return null;
}
public async login(user: any) { // 登录,创建一个登录的token并返回
const { username, id } = user;
const payload = { username, id };
return {
// 调用内置的nestjs的jwt生成token的对象方法
token: 'Bearer ' + this.jwtService.sign(payload),
};
}
}
JwtService这个jwt内置服务,会提供生成token的对象方法,当然还需要在模块中配置一些对应的才能生效
// auth.controller.ts
import { Controller, Request, Post, UseGuards, Get } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
/**
* @UseGuards(AuthGuard('local'))我们使用的AuthGuard是@nestjs/passport自动设置
*/
@Controller('/auth')
export class AuthController {
// 前面也提到过的,注入服务,提供给整个路由控制器类使用
public constructor(private readonly authService: AuthService) { }
/**
* 使用jwt安全认证,登录成功后生成token
*/
@UseGuards(AuthGuard('encry')) // encry 自定义的,默认是local,在local.strategy.ts文件中修改
@Post('/login')
public async login(@Request() req): Promise<{ access_token: string }> { // 登录验证在authService里面的validateUser进行
return this.authService.login(req.user); // 发送登录请求,获取token
}
}
AuthGuard守卫,内置安全认证的守卫,通过传递的字符串来辨别是验证数据还是token解析
1.默认情况下local字符串是对数据进行一系列操作,执行local.strategy.ts方法的validate
2.而后面使用的jwt字符串就是解析token的操作,会在Authentication字段中提取出token并解析出之前生成token放入的数据
3.默认情况下的这字符串可以修改,例如local.strategy.ts的PassportStrategy方法的第二个参数,就是自定义的字符串,然后使用也就用相应的字符串
// auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { LocalStrategy } from './local.strategy';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
@Module({
imports: [
JwtModule.register({
secret: jwtConstants.secret, // 设置secret
signOptions: { expiresIn: '36000s' }, // 设置token的属性,时间为3600*10就是十小时,其余配置可以看jwt的一些相关配置
}),
],
controllers: [AuthController], // 注册控制器
providers: [AuthService, LocalStrategy], // 把AuthService,LocalStrategy注册成提供者
exports: [AuthService], // 把这个服务抛出,给其他模块使用
})
export class AuthModule { }
/**
* jwt的密匙
*/
export const jwtConstants = {
secret: 'secretKey',
};
// jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';
/**
* 解析token的类
* * 命名策略,实现策略时,可以通过向PassportStrategy函数传递第二个参数来为其提供名称。如果你不这样做,每个策略都有一个默认名称(例如,jwt-strategy的'jwt'
*/
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy/*, 'myjwt'*/) {// 自定义成myjwt后,解析也要使用myjwt
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate({id, username}: any) {
return { id , username };
}
}
区别不大,就是调用父类的时候,传递了三个参数
validate,然后重写父类的这个方法会接收到一个token解析后的payload数据,和生成token所用的payload一致
// auth.module.ts
import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
@Module({
imports: [
// PassportModule,使用@nestjs/passport里面的PassportModule模块
// 使用该register()方法可以以相同的方式传递任何标准的Passport自定义选项。可用选项取决于正在实施的策略。
PassportModule.register({ defaultStrategy: 'jwt'/*, session: true */ }), // 默认策略,之后在装饰器中使用就不需要传递
JwtModule.register({
secret: jwtConstants.secret, // 设置secret
signOptions: { expiresIn: '36000s' }, // 设置token的属性,时间为3600*10就是十小时,其余配置可以看jwt的一些相关
}),
],
controllers: [AuthController], // 注册控制器
providers: [AuthService, LocalStrategy, JwtStrategy], // 把AuthService,LocalStrategy,JwtStrategy注册成提供者
exports: [AuthService], // 把这个服务抛出,给其他模块使用
})
export class AuthModule { }
/**
* 在第一种情况下(用户未登录),我们需要执行两个不同的功能:
* * 限制未经身份验证的用户可以访问的路由(即拒绝访问受限制的路由)。我们将以熟悉的方式使用Guards来处理此功能,方法是在受保护的路由上放置一个Guard。
* * 正如您所预料的那样,我们将在此Guard中检查是否存在有效的JWT,因此我们将在稍后成功发布JWT后继续使用此Guard。
*/
增加了PassportModule.register模块和JwtStrategy这个自定义继承实现的服务
使用该register()方法可以以相同的方式传递任何标准的Passport自定义选项。可用选项取决于正在实施的策略。
/**
* 解析token并返回数据
*/
// @UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard()) // 设置了默认策略之后
@Get('me')
public getProfile(@Request() req): any {
return req.user;
}
@UseGuards(AuthGuard('jwt'))方式是在注册的时候没有配置 defaultStrategy: 'jwt'的情况下使用的,配置了就不需要传递'jwt'字符串
yarn add @nestjs/typeorm typeorm mysql --save
// app.module.ts
import { Module } from '@nestjs/common';
// 使用typeorm
import { TypeOrmModule } from '@nestjs/typeorm';
import { Connection } from 'typeorm';
@Module({
imports: [TypeOrmModule.forRoot(
{ // 注意:使用ts或者es6以上的方式,不支持ormconfig.json的方式
type: 'mysql', // 类型
host: 'localhost', // 本地host
port: 3306, // 3306 mysql 端口号
username: 'root', // mysql数据库账号
password: 'password', // 数据库密码
database: 'test', // 对应的数据库
entities: [
__dirname + '/**/entity/*.entity{.ts,.js}',
],
synchronize: true,
}),
],
})
export class AppModule {
// 完成此操作后,TypeORM Connection和EntityManager对象将可用于**在整个项目中进行注入**(无需导入任何模块),
public constructor(private readonly connection: Connection) { }
}
引入,配置数据库连接,entities是typeORM扫描的实体路径,当数据库中不存在该表/集合会创建
(记得不是src,是dist运行自动打包的项目目录)
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "root",
"password": "test",
"database": "test",
"entities": [
"dist/**/entity/*.entity{.ts,.js}"
],
"synchronize": true
}
// entity/users.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Users {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 100, update: false })
username: string;
@Column({ length: 100 })
password: string;
@Column({
length: 10,
default: 'admin',
update: false,
})
type: string;
@Column({ length: 255 })
header: string;
@Column({ length: 100 })
post: string;
@Column({ length: 100 })
info: string;
@Column({ length: 100 })
company: string;
@Column({ length: 100 })
salary: string;
}
typeorm的装饰器的一些使用,可以观看官方文档或者,本人写的恶补typeorm
Nest恶补Typeorm - Typeorm快速入门学习(结合typescript)
TypeOrm官方API文档,跳转
// users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Users } from './entity/users.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(Users) // 注入实体,传递实体类参数
private readonly usersRepository: Repository<Users>, // 类型为Repository的泛型类
) { }
public findAll(): Promise<Users[]> {
return this.usersRepository.find();
}
}
// users.controller.ts
import { Controller, Get, Post, Param } from '@nestjs/common';
import { Users } from './entity/users.entity';
import { UsersService } from './users.service';
@Controller('/user')
export class UsersController {
public constructor(private readonly usersService: UsersService) { }
@Get()
public findAll(): Promise<Users[]> {
return this.usersService.findAll(); // get请求查询全部user数据
}
}
// users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { Users } from './entity/users.entity';
@Module({
imports: [TypeOrmModule.forFeature([Users/*, 'name' */])], // 加入功能模块,为一个数组的实体类 , 第二个参数为一个字符串,表示你在连接数据库设置的name,也是标识多个数据库连接的name
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule { }
TypeOrmModule.forFeature(参数1,参数2) 加入功能模块,为一个数组的实体类 , 第二个参数为一个字符串,表示你在连接数据库设置的name,也是标识多个数据库连接的
// app.module.ts
import { Module } from '@nestjs/common';
// 使用typeorm
import { TypeOrmModule } from '@nestjs/typeorm';
import { Connection } from 'typeorm';
import { UsersModule } from './users.module'
@Module({
imports: [TypeOrmModule.forRoot(
{
type: 'mysql', // 类型
host: 'localhost', // 本地host
port: 3306, // 3306 mysql 端口号
username: 'root', // mysql数据库账号
password: 'password', // 数据库密码
database: 'test', // 对应的数据库
entities: [
__dirname + '/**/entity/*.entity{.ts,.js}',
],
synchronize: true,
}),
UsersModule
],
})
export class AppModule {
// 完成此操作后,TypeORM Connection和EntityManager对象将可用于**在整个项目中进行注入**(无需导入任何模块),
public constructor(private readonly connection: Connection) { }
}
public constructor(private readonly connection: Connection) {}
这个构造函数的目的是可以让Connection和EntityManager对象将可用于**在整个项目中进行注入**(无需导入任何模块)
// users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository, InjectConnection, InjectEntityManager } from '@nestjs/typeorm';
import { Repository, EntityManager, Connection } from 'typeorm';
import { Users } from './entity/users.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(Users) // 注入实体,传递实体类参数
private readonly usersRepository: Repository<Users>, // 类型为Repository的泛型类,生成两个参数,manager和metadata
// 或者把连接数据库设置的name传递连接注入Connection或EntityManager
@InjectConnection(/*'personsConnection'*/) // 获取对应的连接
private readonly connection: Connection,
@InjectEntityManager(/*'personsConnection'*/) // 获取注入的实体管理
private readonly entityManager: EntityManager,
) { }
public findAll(): Promise<Users[]> {
return this.entityManager.find(Users);
// or
// return this.connection.manager.find(Users);
// return this.usersRepository.find();
}
}
// custom.repository.ts
import { EntityRepository, Repository } from 'typeorm';
import { Users } from './entity/users.entity';
/**
* 定制存储库
* * TypeORM提供了一个称为自定义存储库的功能。自定义存储库允许您扩展基本存储库类,并使用几种特殊方法对其进行丰富
* * 下一步是将实例化责任委派给Nest。为此,我们必须将AuthorRepository类传递给TypeOrm.forFeature()方法。
* * 之后在imports里面使用的TypeOrmModule.forFeature([Users] 替换成TypeOrmModule.forFeature([UsersRepository]
*/
@EntityRepository(Users) // 扩展了基本的存储库操作等
export class UsersRepository extends Repository<Users> {
constructor( // 使用Object.assign对this上面添加连接成功后的操作对象
connection: Connection,
) {
super();
Object.assign(this, connection.getRepository(Users));
}
// 扩展基本库,增加以下方法,通过id查找单条数据
public findById(id: number): Promise<Users> {
return this.findOne({ id });
}
// 扩展基本库,增加add这个增加数据的操作
public add(users: Users): Promise<Users> {
return this.save(users);
}
}
在存储库扩展的情况下,继承Repository 这个泛型类,可以使用先前默认的find,findOne....等等等增删改查的方法
// users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { UsersRepository } from './custom.repository';
@Module({
// imports: [TypeOrmModule.forFeature([Users/*, 'name' */])], // 加入功能模块,为一个数组的实体类 , 第二个参数为一个字符串,表示你在连接数据库设置的name,也是标识多个数据库连接的name
imports: [TypeOrmModule.forFeature([UsersRepository])], // 注入自定义扩展存储库后的实体
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule { }
import { Injectable } from '@nestjs/common';
import { InjectConnection, InjectEntityManager } from '@nestjs/typeorm';
import { EntityManager, Connection } from 'typeorm';
import { Users } from './entity/users.entity';
import { UsersRepository } from './custom.repository';
@Injectable()
export class UsersService {
constructor(
private readonly usersRepository: UsersRepository, // 类型为Repository的泛型类,生成两个参数,manager和metadata
// 或者把连接数据库设置的name传递连接注入Connection或EntityManager
@InjectConnection(/*'personsConnection'*/) // 获取对应的连接
private readonly connection: Connection,
@InjectEntityManager(/*'personsConnection'*/) // 获取注入的实体管理
private readonly entityManager: EntityManager,
) { }
public findAll(): Promise<Users[]> {
// return this.entityManager.find(Users);
// or
// return this.connection.manager.find(Users);
return this.usersRepository.find();
}
public getFindOne(id: number): Promise<Users> {
return this.usersRepository.findById(id); // 使用自定义的存储库操作
}
public addUser(user: Users): Promise<Users> { // 添加一条数据的方法
return this.userRepository.add(user);
}
}
使用了扩展的UsersRepository存储库后,会增加两个自定义的方法,findById和add
// users.controller.ts
import { Controller, Get, Post, Param, Body } from '@nestjs/common';
import { Users } from './entity/users.entity';
import { UsersService } from './users.service';
@Controller('/user')
export class UsersController {
public constructor(private readonly usersService: UsersService) { }
@Get()
public findAll(): Promise<Users[]> {
return this.usersService.findAll();
}
@Post('/select/:id') // 通过id查询单个用户信息
public findOne(@Param('id') id: number): Promise<Users> {
return this.usersService.getFindOne(id);
}
@Post('/add') // 通过id查询单个用户信息
public addUser(@Body() user: Users): Promise<Users> {
return this.usersService.addUser(user);
}
}
这样扩展了存储库,也可以进行一些复杂的对数据库的操作封装,本人只是简单使用
// async.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TypeOrmConfigService } from './typeOrmConfig.service';
/**
* 异步配置存储库
* * 异步传递存储库模块选项而不是静态传递。在这种情况下,请使用该forRootAsync()方法,该方法提供了几种处理异步配置的方法。
* * 1.工厂功能
*/
@Module({
imports: [TypeOrmModule.forRootAsync({
// 方式1,直接使用useFactory方法配置,返回一个连接对象
useFactory: () => ({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
// 方式2 使用以下useClass语法,配置一个创建连接的服务,
// useClass: TypeOrmConfigService,
})],
})
export class AsyncModule { }
// typeOrmConfig.service.ts
import { Injectable } from '@nestjs/common';
import { TypeOrmOptionsFactory, TypeOrmModuleOptions } from '@nestjs/typeorm';
@Injectable()
export class TypeOrmConfigService implements TypeOrmOptionsFactory { // 实现选项factory工厂
createTypeOrmOptions(): TypeOrmModuleOptions { // 返回一个模块选项
return {
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
};
}
}
暂时了解异步配置即可,也可以在正常项目中使用