在应用里我们可以给用户分配一些角色,一般这些角色可以存储在数据仓库里,用户的访问应用的时候,可以设置接口要求的用户角色,如果访问这个接口的用户没有要求的用户角色,应用可以拒绝提供服务。这个就是基于角色的权限检查。
roles 装饰器
我们可以使用装饰器设置应用接口需要的用户角色,先在应用里生成一个装饰器,在终端,执行 nest g d roles,生成一个叫 roles 的装饰器。
在项目里,打开 roles.decorator 可以观察一下,这里定义了一个叫 Roles 的装饰器,使用这个装饰器会用 SetMetadata 设置一个元数据,这个数据的名字叫 roles,等会儿我们可以通过 reflector 获取到名字是 roles 的元数据。
这个数据的值就是使用 Roles 这个装饰器的时候提供的参数值。这个参数值应该会在一个数组里。
app.controller
打开 app.controller,在这个控制器里可以创建一个接口,用 @Get 装饰一下,接口地址是 admin,再用刚才创建的 Roles 装饰器装饰一下接口的处理方法,提供一个 admin 参数值。
方法的名字是 getAdmin, 返回的值是一个外星人小图标。
预览
在 http 客户端可以测试一下,配置一个请求,请求方法用 get,地址是 /admin, 发送这个请求,得到的响应会是一个外星人小图标。
roles 守卫
现在我们虽然设置了接口需要的用户角色,不过并没有检查用户角色,检查用户角色可以使用 Guard ,也就是守卫。先在项目里生成一个守卫,执行 nest g gu roles,生成一个叫 roles 的守卫。
在项目里打开 roels.guard 这个守卫。守卫里都会有一个 canActivate 方法,如果这个方法返回 true,就可以继续访问,如果返回的值是 false,应该会拒绝用户的访问,客户端默认会收到一个状态码是 403 的响应。先让它 return false。
注册守卫
我们可以在应用的全局注册使用这个守卫,打开 app.module,在 providers 里面,添加一个对象,里面有个 provide 属性,值设置成 APP_GUARD,再添加一个 useClass,值是 RolesGuard 这个类。这样就会在应用的全局,使用 RolesGuard 这个守卫。
roles 守卫
在 http 客户端,访问一下这个接口,这次会得到一个状态码是 403 的响应。因为现在应用里使用了 RolesGuard 这个守卫,现在这个守卫里的 canActivate 方法返回的值一直是 false,所以会拒绝所有的访问。
在这个方法里,我们可以检查请求用户的用户角色,然后可以对比接口处理方法要求的角色,如果用户角色列表里包含要求的用户角色,就放行,如果不包含就拒绝访问。
在真实的应用里,可以检查用户身份,用户登录成功以后给用户签发令牌,用户在访问的时候要在访问里带着这个签发的令牌,应用可以验证令牌的有效性,然后判断出请求的用户是谁,也可以得到请求的用户的用户角色。
这里为了演示,我们先简单的通过一个请求头部来设置用户的用户角色,在这个请求里添加一个头部数据,名字是 X-User-Roles, 对应的值是 manager。
再回到 roles 守卫,现在我们要得到用在类或者处理器方法上面的使用 Roles 装饰器设置的元数据的值。这里要用的是 reflector。
在这个守卫里面可以注入使用它,先添加一个 constructor,添加一个属性,private reflector,把它的类型设置成 Reflector。这样就可以在这个类的方法里使用 reflector 了。
然后在 canActivate 这个方法里面,声明一个 requiredRoles,它的值用一下 this.reflector.getAllAndOverride,获取到 roles 这个元数据,再提供一个数组,里面设置一下这个元数据所在的位置,这里用一下 context.getHandler(),意思是得到用在处理器方法上的名字是 roles 的元数据的值。
下面可以判断一下,如果 !requiredRoles 就 return true。 如果没有要求的用户角色,可以直接放行请求。
现在要得到当前请求用户的用户角色,这个用户角色我们为了演示,直接是在请求的头部里设置的,解构一下请求,需要的是 headers,继续解构它,需要的是 x-user-roles 这个头部,重新起个名字叫 userRoles。获取到请求,可以执行 context.switchToHttp,继续调用 getRequest。
判断一下,如果 !userRoles 可以 return false,如果用户没有用户角色,就拒绝访问。
return 的值可以用一下 requiredRoles.some,当前项目是 role 类型是 string,在这个回调里返回的值可以用一下 userRoles.split 按逗号把字符串分成一个数组,继续调用 includes,把 role 交给这个方法。
这两行代码的意思就是,检查用户的用户角色,看一下这些角色里有没有处理器方法里要求的角色,如果有就返回 true,放行请求,如果没有就返回 false,拒绝提供服务。
测试
下面可以测试一下,在 http 客户端,发送一下这个请求,得到的响应是 403,因为这个接口的处理器要求的用户角色是 admin,但是当前请求的用户只有 manager 这个角色。所以请求这个接口得到的就是 403 这个响应。
可以再设置一下这个接口处理器要求的用户角色,再添加一个 manager ,在 http 客户端,再发送一下这个请求,这次就可以得到正常的响应了,因为这个用户拥有一个 manager 角色。这个角色包含在了这个接口处理器要求的用户角色列表里。