守卫

  1. 守卫是一个 @Injectable() 装饰的类,守卫应该实现 CanActivate 接口;

  2. 守卫是一个单独的责任,用于权限验证;虽然访问限制逻辑通常在中间件内,但中间件是非常笨的,它不知道调用 next() 后会执行哪个处理程序;而守卫可以访问 ExecutionContext 对象,确切知道将要执行什么;所以在Nextjs中,可以用守卫做权限判断,也可以用中间件;

  3. 创建守卫

    nest g guard guard/auth
    
    1. 生成 guard/auth.guard.ts
        import { Observable } from 'rxjs';
    
        @Injectable()
        export class AuthGuard implements CanActivate {
            canActivate(context: ExecutionContext):boolean | Promise | Observable {
                console.log('守卫执行...');
                return true;  // 返回 true 表示当前具有访问权限,返回 false 则拒绝访问
            }
        }
    
    1. 守卫可以全局配置,也可以在控制器中使用,还可以在对应的模块中配置。
  4. 控制器中使用守卫

    1. 配置在控制器上,访问其中的所有控制器方法都会经过守卫校验;
        import { UseGuards } from '@nestjs/common';
        import { AuthGuard } from '../../../guard/auth.guard';
    
        @Controller('user')
        @UseGuards(AuthGuard)
        export class UserController {}
    
    1. 配置在控制器方法上,只有访问此路由时才会经过守卫校验;
        @Controller('user')
        export class UserController {
            @Get('add')
            UseGuards(AuthGuard)
            add() {
                return '----add----'
            }
        }
    
  5. 全局配置守卫:main.ts,所有路由都经过守卫校验

    1. main.ts
    import { AuthGuard } from './guard/auth.guard';
    
    app.useGlobalGuards(new AuthGuard());
    
    1. 这种方式的注册不依赖于任何模块,所以在依赖注入方面,这种注册全局守卫的方式不能插入依赖项, 因为它们不属于任何模块。为此,还可以直接在任何模块中设置一个守卫,它同样是一个全局守卫:
    import { APP_GUARD } from '@nestjs/core';
    import { AuthGuard } from './guard/auth.guard';
    
    @Module({
        providers: [{ provide: APP_GUARD,  useClass: AuthGuard }],
        ......
    })
    export class XxxModule { }
    
  6. 在守卫中获取Cookie和Session

    canActivate(context: ExecutionContext):boolean | Promise | Observable {
        // 获取到Request对象,从而获取到Cookie和Session
        let req = context.switchToHttp().getRequest();
        // 获取Session
        let uname = req.session.uname;
        return true;
    }
    

反射器

  1. 守卫最重要的特征在于:执行上下文
  2. 守卫还不知道角色,每个处理程序允许哪些角色访问,也就是将路由和角色匹配起来,这就是自定义元数据所发挥的作用;
  3. @SetMetadata() 装饰器将定制的元数据附加到路由处理程序上,这些元数据提供了角色数据;守卫通过这些数据做出决策;
    @Post()
    @SetMetadata('roles', ['admin'])
    create() { }
    
    1. roles 为键、['admin'] 为特定值的元数据,被附加到 create() 方法上;
    2. 通过自定义装饰器@Roles(),封装 @SetMetadata()
        roles.decorator.ts
    
        import { SetMetadata } from '@nestjs/common';
        export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
    
        @Post()
        @Roles('admin')
        create() { }
    
  4. ExecutionContext:执行上下文对象,继承自ArgumentsHost
    1. ArgumentsHost是传递给原始处理程序的参数的包装器,ExecutionContext在此基础上扩展了getClass()、getHandler()
    2. getClass():获取处理程序所在的控制器类,是Controller类型,而不是实例;
    3. getHandler():获取将要调用的处理程序,也就是控制器方法。
  5. 在守卫中,通过反射器 Reflector 访问自定义的元数据;
    import { Reflector } from '@nestjs/core';
    
    @Injectable()
    export class RolesGuard implements CanActivate {
        // 依赖注入反射器对象
        constructor(private readonly reflector: Reflector) {}
    
        canActivate(context: ExecutionContext): boolean {
            // 根据元数据的键,反射获取控制器方法上的值:roles -- ['admin']
            const roles = this.reflector.get('roles', context.getHandler());
            if (!roles) {
                return true;  // 访问的控制器方法没有配置任何角色,都可以访问
            }
            // 获取请求对象
            const request = context.switchToHttp().getRequest();
            // 获取请求对象中的用户对象,此处只是假设用户在请求对象中
            const user = request.user;
            // 判断用户是否具有控制器方法上配置的角色权限
            const hasRole = () => user.roles.some((role) => roles.includes(role));
            return user && user.roles && hasRole();
        }
    }
    
    1. 如果没有权限,即守卫返回falseNest默认响应:
        {
            "statusCode": 403,
            "message": "Forbidden resource"
        }
    
    1. 而实际上,返回false时,守卫会抛出一个 HttpException 异常;如果希望响应不同的错误内容,则手动抛出异常;
        throw new UnauthorizedException();
    
    1. 由守卫引发的任何异常都将由异常层(全局异常过滤器和应用于当前上下文的任何异常过滤器)处理。

你可能感兴趣的:(守卫)