前言
默认你已经初步了解nestjs概念和使用方式。
本文使用 typeorm连接mysql,加密使用bcrypt,校验使用 password基础库,使用jsonwebtoken, 文档swagger。
本文无任何参考价值,后期完成后直接将代码发布。
视频参考 Nestjs开发《全栈之巅》第二章 - p7-基于NestJs+Passport策略的用户登录
基础文档
官网
nestjs 中文
认证(Authentication)
authentication
认证
表设计
RBCA权限表的设计
我问使用最简单的 用户-角色-权限
user
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToMany, JoinTable, JoinColumn, BeforeInsert, BeforeUpdate } from 'typeorm'; import { RoleEntity } from './role.entity'; import * as bcrypt from 'bcrypt';
@Entity('user') export class UserEntity { @PrimaryGeneratedColumn('uuid') id: string;
@Column('varchar', {unique: true}) username: string;
@Column('varchar') password: string;
@CreateDateColumn({ type: 'timestamp', name: 'create_time', comment: '创建时间' }) createTime: Date;
@UpdateDateColumn({ type: 'timestamp', name: 'update_time', comment: '更新时间' }) updateTime: Date;
@ManyToMany(type => RoleEntity, role => role.users) @JoinTable({ name: 'user_role', joinColumns: [ {name: 'user_id'}, ], inverseJoinColumns: [ {name: 'role_id'}, ], }) roles: RoleEntity[];
@BeforeInsert() @BeforeUpdate() async hashPassword() { this.password = await bcrypt.hash(this.password, 12); }
async comparePassword(password: string) { return await bcrypt.compare(password, this.password); } }
|
role
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToMany, JoinTable } from 'typeorm'; import { UserEntity } from './user.entity'; import { PermissionEntity } from './permission.entity';
@Entity('role') export class RoleEntity { @PrimaryGeneratedColumn('uuid') id: string;
@Column('varchar', {unique: true}) name: string;
@Column('varchar') alias: string;
@Column('varchar') desc: string;
@CreateDateColumn({ type: 'timestamp', name: 'create_time', comment: '创建时间' }) createTime: Date;
@UpdateDateColumn({ type: 'timestamp', name: 'update_time', comment: '更新时间' }) updateTime: Date;
@ManyToMany(type => UserEntity, user => user.roles, { cascade: true }) users: UserEntity[];
@ManyToMany(type => PermissionEntity, perm => perm.roles) @JoinTable({ name: 'role_perm', joinColumns: [ {name: 'role_id'}, ], inverseJoinColumns: [ {name: 'perm_id'}, ], }) perms: PermissionEntity[]; }
|
permission
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToMany } from 'typeorm'; import { RoleEntity } from './role.entity';
@Entity('permission') export class PermissionEntity { @PrimaryGeneratedColumn() id: number;
@Column() pid: number;
@Column() url: string;
@Column() code: string;
@Column() perms: string;
@Column() name: string;
@CreateDateColumn({ type: 'timestamp', name: 'create_time', comment: '创建时间' }) createTime: Date;
@UpdateDateColumn({ type: 'timestamp', name: 'update_time', comment: '更新时间' }) updateTime: Date;
@ManyToMany(type => RoleEntity, role => role.perms, { cascade: true }) roles: RoleEntity[]; }
|
编码逻辑
用户登录后获取token, 每次请求都带token,在需要验证权限(guard)的地方,解析token,获取到用户的角色信息,在根据角色信息查看所对应的权限,查看所对应的权限是否有请求需要的权限。有就通过,没有就不通过
编码
local.strategy.ts
主要作用 获取到用户名和密码,查找验证用户,通过后会将用户信息挂载到request请求上,request.user。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { Strategy } from 'passport-local'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { AuthService } from './auth.service';
@Injectable() export class LocalStrategy extends PassportStrategy(Strategy, 'local') { constructor(private readonly authService: AuthService) { super({ usernameField: 'username', passwordField: 'password', }); }
async validate(username: string, passport: string) { const user = await this.authService.validateUser(username, passport); if (!user) { throw new UnauthorizedException(); } return user; } }
|
jwt.strategy.ts
主要作用 生成token 和验证 token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { Injectable } from '@nestjs/common'; import { jwtConstants } from './constants'; import { AuthService } from './auth.service';
@Injectable() export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(private readonly authService: AuthService) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKey: jwtConstants.secret, }); }
async validate(id) { const user = await this.authService.findUserById(id); return user; } }
|
role.guard.ts
有验证角色权限的逻辑
token已经在jwt.strategy.ts验证过token的有效性及将token解析挂载到了request,无用户信息直接返回。
获取到用户的角色,根据角色查看权限,在比对请求路由的权限是否在用户所属权限里面,没有就返回权限不足,有就返回用户信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Request } from 'express';
@Injectable() export class DemoGuard implements CanActivate { constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean { const request: Request = context.switchToHttp().getRequest(); const user = (request as any).user; if (!user) { return false; } const perms = this.reflector.get<string[]>('perms', context.getHandler()); if (!perms) { return true; }
const userPerms = await ....
const hasPerm = () => userPerms.some((perm) => perms.includes(perm)) return user && hasPerm(); } }
|
如有问题可联系 Email:afacode@outlook.com 或 微信:afacode