At present, due to the migration of Nodejs framework, the original Typerx back-end project is migrated to the NestJS framework, so as to achieve the permission management part, especially to share with you. Project Address:
typerx
nestx
NestJs official character control introduction
Because this article is mainly about the rights management part of the introduction, so we have a temporary understanding of user identity knowledge, for more information about user login, please refer to other related documents.
-
Guards Guards are annotated Guards who describe what the access limits of the controller they are modifying are. He should implement the CanActivate interface. Guards have a single responsibility to determine whether a request can be routed. ** Note that Guards comes after each middleware, but ahead of interceptor and PIPE. **
-
There are two concepts that we need to understand before we look at permissions: Authentication and Authorization. Yes, they are very similar, but actually they are different.
Authentication and Authorization
Authentication -> 401
Authorization is mainly role identification, meaning like asking if your ID is local (what is the role, do you have permissions) -> 403.
(Authorization guard# and role-based authentication# are not inverted?) auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
returnvalidateRequest(request); }}Copy the code
roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true; }}Copy the code
With AuthGuard and RolesGuard, our controller can be written like this, of course you can bind multiple controllers separated by commas.
Use RolesGuard for dependency injection
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
Copy the code
If you do not use dependency injection, you can also use new to create an instance for it.
@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}
Copy the code
Gateways and Microservices do not need to be annotated globally, but gateways and Microservices do not need to be verified.
const app = await NestFactory.create(ApplicationModule);
app.useGlobalGuards(new RolesGuard());
Copy the code
- We now have the RolesGuard, but we still need to associate the character with the controller.
Just look at cats. Controller. Ts
@Post()
@SetMetadata('roles'['admin'])
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
Copy the code
Now that the role is associated with the controller, we can restrict access to the interface only to the administrator, but the code is a little ugly and not very neat, right? Let’s try another way:
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
Copy the code
Isn’t that better? This main we add a below the decorator can be
roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
exportconst Roles = (... roles: string[]) => SetMetadata('roles', roles);
Copy the code
Roles.guard.ts implementation details
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler()); // Get the role group information from the controller annotations.if(! roles) {return true; } const request = context.switchToHttp().getRequest(); const user = request.user; const hasRole = () => user.roles.some((role) => roles.includes(role)); // Whether the role is matchedreturnuser && user.roles && hasRole(); }}Copy the code
The main point here is to get the User from the context and extract the role from the User and the annotation of the role in the controller to see if there is an intersection match, and if there is, let go.
In general, the official documents are not very much, and so down, the whole scheme is just a simple role guardian, not yet able to complete the complex authority system, but it can also meet the general non-flexible configuration of the system.
Nestx permission system requirements
- Roles can be customized. Here is:
-
Under the menu, there are various types of permission nodes, such as read and write control. Here is:
-
Roles can be configured under the management menu. The illustration is the same.
Reference resources
- Github.com/nestjs-comm…
- Github.com/casbin/node…
nest-access-control
import { Get, Controller, UseGuards } from '@nestjs/common';
import { UserRoles, UseRoles, ACGuard } from 'nest-access-control';
import { AppService } from './app.service';
import { AuthGuard } from './auth.guard';
import { AppRoles } from 'app.roles';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@UseGuards(AuthGuard, ACGuard)
@UseRoles({
resource: 'video',
action: 'read',
possession: 'any',
})
@Get()
root(@UserRoles() userRoles: any) {
returnthis.appService.root(userRoles); }}Copy the code
Guard annotations are interesting in that they are divided into resources, actions, and permissions, and seem to fit our needs.
There is a RolesBuilder class for creating definitions:
// app.roles.ts
export enum AppRoles {
USER_CREATE_ANY_VIDEO = 'USER_CREATE_ANY_VIDEO',
ADMIN_UPDATE_OWN_VIDEO = 'ADMIN_UPDATE_OWN_VIDEO',}export const roles: RolesBuilder = new RolesBuilder();
roles
.grant(AppRoles.USER_CREATE_ANY_VIDEO) // define new or modify existing role. also takes an array.
.createOwn('video') // equivalent to .createOwn('video'[The '*'])
.deleteOwn('video')
.readAny('video')
.grant(AppRoles.ADMIN_UPDATE_OWN_VIDEO) // switch to another role without breaking the chain
.extend(AppRoles.USER_CREATE_ANY_VIDEO) // inherit role capabilities. also takes an array
.updateAny('video'['title']) // explicitly defined attributes
.deleteAny('video');
Copy the code
At present, the way we see is mainly stored in the form of files, which we need to place in the database. This module can be referred to for subsequent analysis.
node-casbin
Casbin seems to be a more popular permission module, a variety of languages, to provide a full range of functions.
- Supports custom request format. The default request format is {subject, object, action}.
- It has two core concepts: access control model and policy.
- Support multi-layer role inheritance in RBAC, where not only principals can have roles, but also resources can have roles;
- Super users, such as root and Administrator, can access any resources without authorization policies.
- Supports multiple built-in operators, such as keyMatch, to facilitate the management of path-like resources. For example, /foo/bar can be mapped to /foo*.
Current examples of node-Casbin integration into NestJS are Nest-Casbin and nt-Casbin, but both modules are relatively crude, providing only simple wrapper calls to both services
enforcer.enforce(sub, obj, act);
Copy the code
There is no encapsulated annotation tag. The above is some preparation and understanding analysis, concrete implementation to continue.