跟我一起学.NetCore之熟悉的接口权限验证不能少(Jwt)
前言
权限管控对于一个系统来说是非常重要的,最熟悉不过的是菜单权限和数据权限,上一节通过Jwt实现了认证,接下来用它实现接口权限的验证,为什么不是菜单权限呢?对于前后端分离而言,称其为接口权限感觉比较符合场景(我是这么理解的);数据权限牵涉到具体业务,这里就不说啦!
正文
对于一些比较简单的系统,访问角色可能只有固定的几种,比如一些产品管理系统,通常只有管理员、维护员、用户三种权限,管理员拥有整个系统的权限,维护员只能访问产品维护相关页面和操作,用户只能访问产品的一些信息,如果类似这种情况,可以直接指定角色的方式进行权限管控,如下:
案例代码直接使用上一节的项目,借用上次认证那块代码(偷懒太明显),如果没看上一篇的小伙伴,去瞅瞅认证那块内容([跟我一起学.NetCore之WebApi接口裸奔有风险(Jwt)](http://mp.weixin.qq.com/s?__biz=MzU1MzYwMjQ5MQ==&mid=2247484105&idx=1&sn=9322333e9fc39a0a0daf2c4901cf1efd&chksm=fbf11e1dcc86970be6a815b946d43b0f6753f89d3fb6b447b21a1e10373be44cbc59e0ebfe15&scene=21#wechat_redirect)),随便敲敲代码,这节要用(别说我,我是有苦衷的,想让小伙伴多撸代码~~);如果只是想熟悉知识点,也可以继续往下看的,不废话,直接开始:

注:[Authorize]加在控制器上时,该控制器下所有的接口都受保护;
为了方便测试,如上图所示,增加一个产品控制器,针对不同人员模拟了三个接口,因为接口受到保护,只能通过获取到的Token才能正常访问,这里就不截图演示了。Token调用User中的Login接口获取,详细请参考(跟我一起学.NetCore之WebApi接口裸奔有风险(Jwt))。哎呀,还是上个生成Token的代码图:

下面将三个接口指定为不同角色访问,然后运行访问如下:

通过运行测试可知,当增加了对应角色要求之后,尽快Token验证正确,也不能正常调用接口,返回403禁止访问。已经为接口指定了角色,那要如何才能正常调用呢?其实只要在生成Token的时候指定对应角色即可正常访问对应角色的接口,如下代码优化:

如上运行所示,在生成Token时指定了角色为Admin,则这个Token只能访问指定角色为Admin的接口,其他接口是不能访问的。如需要访问其他接口,同样需要在生成Token的时候添加对应的角色,如下:

运行效果这里就不截图演示了,小伙伴们自己试试。
看到这,小伙伴们肯定会问,管理员角色肯定是所有接口都能访问,拿到了Token接下来该咋办,每个接口都加管理员角色对应的特性吗?对于多个角色访问同一个接口的情况,一般会为授权定义策略来实现,如下:

运行效果如下:

小注意点:
多个角色或运算:多个角色只要有其中一个就可以访问;
多个角色且运算:同时得有多个角色才能访问接口;

到这,相信小伙伴已经忍不住要问:不管是角色还是策略的方式,角色都写死了,如果角色动态分配权限咋搞? 是的,上面的方式只适合对权限管控比较简单的项目,绝大数的项目权限肯定是动态分配的,即根据需求,可以针对用户进行访问权限配置,所以接下来就说说这块咋搞。
这次增加的代码稍微有点多,代码都有注释,另外跟着我标注的步骤走,绝对No Problem:



通过以上步骤就完成动态权限的验证了,是不是很给力,这里需要注意一下几个点:
后续用到IHttpContextAccessor需要进行注册;

用户ID在登录的时候要放入Payload中,后续权限验证时要用;

API中使用的策略名称要和定义的一致;

以上案例演示中,在登录的时候模拟权限数据存入内存,对于一些用户数据不大的项目,这种方式还不错,但是对于用户数量比较大或者分布式部署的项目,建议将权限数据存入Redis等缓存数据库中,存取统一的同时也能减少对数据库的访问压力,总不能每次请求过来都从数据库中获取权限数据进行校验。
小知识点:
- 通过获取用户时,访问的接口必须要有Authorize特性,否则只能通过自己解析Token获取;
 - 如果在统一受保护的控制器中,有个别接口不需要权限验证,可以为其标注AllowAnonymous特性即可;
 
校验权限逻辑的完整代码如下:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SwaggerDemo.Permission
{
    /// <summary>
    /// 权限处理的关键类
    /// </summary>
    public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
    {
        /// <summary>
        /// 通过IHttpContextAccessor可以获取HttpContext相关信息,但一定要注册服务
        /// </summary>
        private readonly IHttpContextAccessor _accessor;
        /// <summary>
        /// 用于判断请求是否带有凭据和是否登录
        /// </summary>
        public IAuthenticationSchemeProvider Scheme { get; set; }
        /// <summary>
        ///  构造函数注入
        /// </summary>
        public PermissionHandler(IHttpContextAccessor accessor,IAuthenticationSchemeProvider scheme)
        {
            this._accessor = accessor;
            Scheme = scheme;
        }
        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
        {
            try
            {
                //拿到HttpContext就无所不能啦
                var httpContext = _accessor.HttpContext;
                //判断资源数据中权限列表中是否有权限
                if (!requirement.Permissions.Any())
                {
                    //没有直接返回无权限,也可以重新获取权限,实现不退出重新登录就可获取最新权限
                    context.Fail();
                }
                //判读请求是否拥有凭据,即是否登录
                var defaultAuthenticate = await Scheme.GetDefaultAuthenticateSchemeAsync();
                if (defaultAuthenticate == null)
                {
                    context.Fail();
                }
                var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);
                //不为空代表登录成功,为空登录失败
                if (result?.Principal != null)
                {
                    // 获取生成Token时放在payload里的userId
                    string userId = _accessor.HttpContext.User.FindFirst("userId").Value;
                    // 获取当前请求的地址
                    string requestUrl = httpContext.Request.Path.Value.ToLower();
                    // 从权限表中查找当前用户是否有当前请求地址的权限
                    var permission = requirement.Permissions.FirstOrDefault(a => a.Url.ToLower() == requestUrl && a.UserId == userId);
                    // 如果没找到,代表没有权限
                    if (permission == null)
                    {
                        context.Fail();
                    }
                    // 如果找到,就继续往下执行
                    context.Succeed(requirement);
                }
                else
                {
                    // 获取不到对应值就返回无权限
          context.Fail();
                }
            }
            catch (Exception ex)
            {
                context.Fail();
            }
        }
    }
}
总结
关于权限验证这块之前大多都是在MVC的授权过滤器中进行完成的, 本来想在说过滤器那块一起说说权限验证的,但感觉关于Jwt的放在一块比较合适。关于Jwt其实还有一个比较关键的点,就是对于Token的处理问题,比如刷新Token、手动失效Token等,这块后续单独整理一篇内容分享。下次先说说过滤器的执行顺序。
关于代码,其实现在在写案例的时候感觉已经开始复杂了,后面整理整理会放在github上,如果急需的可以私下找我,我单独发给小伙伴。
一个被程序搞丑的帅小伙,关注"Code综艺圈",识别关注跟我一起学~~~

撸文不易,莫要白瞟,三连走起~~~~
跟我一起学.NetCore之熟悉的接口权限验证不能少(Jwt)的更多相关文章
- 从零开始学 Java - Spring AOP 实现用户权限验证
		
每个项目都会有权限管理系统 无论你是一个简单的企业站,还是一个复杂到爆的平台级项目,都会涉及到用户登录.权限管理这些必不可少的业务逻辑.有人说,企业站需要什么权限管理阿?那行吧,你那可能叫静态页面,就 ...
 - 跟我一起学.NetCore之MVC过滤器,这篇看完走路可以仰着头走
		
前言 MVC过滤器在之前Asp.Net的时候就已经广泛使用啦,不管是面试还是工作,总有一个考点或是需求涉及到,可以毫不疑问的说,这个技术点是非常重要的: 在之前参与的面试中,得知很多小伙伴只知道有一两 ...
 - 跟我一起学.NetCore之选项(Options)核心类型简介
		
前言 .NetCore中提供的选项框架,我把其理解为配置组,主要是将服务中可供配置的项提取出来,封装成一个类型:从而服务可根据应用场景进行相关配置项的设置来满足需求,其中使用了依赖注入的形式,使得更加 ...
 - 跟我一起学.NetCore之静态文件处理的那些事
		
前言 如今前后端分离开发模式如火如荼,开发职责更加分明(当然前后端一起搞的模式也没有完全褪去):而对于每个公司产品实施来说,部署模式会稍有差别,有的会单独将前端文件部署为一个站点,有的会将前端文件和后 ...
 - 跟我一起学.NetCore之WebApi接口裸奔有风险(Jwt)
		
前言 撸码需谨慎,裸奔有风险.经常在一些技术交流群中了解到,还有很多小伙伴的项目中Api接口没有做任何安全机制验证,直接就裸奔了,对于一些临时项目或是个人小项目还好,其余的话,建议小伙伴们酌情考虑都加 ...
 - NETCore使用带有权限验证的Swagger
		
原文:NETCore使用带有权限验证的Swagger 文章目录 Swagger 什么是Swagger NuGet安装 Startup注册Swagger 设置默认首页打开Swagger 为接口添加注释 ...
 - 跟我一起学.NetCore之文件系统应用及核心浅析
		
前言 在开发过程中,肯定避免不了读取文件操作,比如读取配置文件.上传和下载文件.Web中html.js.css.图片等静态资源的访问:在配置文件读取章节中有说到,针对不同配置源数据读取由对应的ICon ...
 - 跟我一起学.NetCore之日志(Log)模型核心
		
前言 鲁迅都说:没有日志的系统不能上线(鲁迅说:这句我没说过,但是在理)!日志对于一个系统而言,特别重要,不管是用于事务审计,还是用于系统排错,还是用于安全追踪.....都扮演了很重要的角色:之前有很 ...
 - 跟我一起学.NetCore之日志作用域及第三方日志框架扩展
		
前言 上一节对日志的部分核心类型进行简单的剖析,相信现在再使用日志的时候,应该大概知道怎么一回事了,比如记录器是怎么来的,是如何将日志内容写入到不同目的地的等:当然还有很多细节没深入讲解,抽时间小伙伴 ...
 
随机推荐
- 【HttpRunner v3.x】笔记 ——1. 环境安装
			
一.环境说明 HttpRunner 是一个基于 Python 开发的测试框架,可以运行在 macOS.Linux.Windows 系统平台上.笔者使用的是windows系统,所以后续都是基于windo ...
 - 牛顿迭代法解非线性方程组(MATLAB版)
			
牛顿迭代法,又名切线法,这里不详细介绍,简单说明每一次牛顿迭代的运算:首先将各个方程式在一个根的估计值处线性化(泰勒展开式忽略高阶余项),然后求解线性化后的方程组,最后再更新根的估计值.下面以求解最简 ...
 - codewars贪吃蛇算法题目
			
有这样一个题目: Given an n x n array, return the array elements arranged from outermost elements to the mid ...
 - 实验 1:Mininet 源码安装和可视化拓扑工具
			
实验 1:Mininet 源码安装和可视化拓扑工具 一.实验目的 掌握 Mininet 的源码安装方法和 miniedit 可视化拓扑生成工具. 二.实验任务 使用源码安装 Mininet 的 2.3 ...
 - Native Comments
			
local variables referenced from a Lambda expression must be final or effectively final. Lambda表达式中引用 ...
 - Python爬取NBA虎扑球员数据
			
虎扑是一个认真而有趣的社区,每天有众多JRs在虎扑分享自己对篮球.足球.游戏电竞.运动装备.影视.汽车.数码.情感等一切人和事的见解,热闹.真实.有温度. 受害者地址 https://nba.hupu ...
 - 关于Nginx mmap(MAP_ANON|MAP_SHARED, 314572800)报错
			
mmap 报错解决 今天修改了一下测试环境的Nginx的nginx.conf,然后做检测的时候报了一个错误 /usr/local/bin/nginx -c /usr/local/etc/openres ...
 - oracle数据处理之sql*loader(一)
			
SQL*Loader是oracle提供的可以从多种平面文件中向数据库中加载数据的工具,它比较适合业务分析类型数据库(数据仓库);使用sqlldr工具可以在很短的时间内向数据库中加载大量的数据,像把制作 ...
 - 简单地 Makefile 书写
			
注意事项 每个标签分支前都不能用空格,必须用tab 标签外调用bash命令用 $(shell -),标签内可以正常使用 标签后可以指定其他标签,执行顺序是先执行其他标签,而后在执行自己 比如 all: ...
 - nginx.config 多个方案解决跨域问题
			
#user nobody; user sam owner; worker_processes 1; #error_log logs/error.log; #error_log logs/error.l ...