欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ASP.NET Core 3.0轻量级角色API控制授权库

程序员文章站 2022-03-01 13:21:50
说明asp.net core 3.0 一个 jwt 的轻量角色/用户、单个api控制的授权认证库最近得空,重新做一个角色授权库,而之前做了一个角色授权库,是利用微软的默认接口做的,查阅了很多文档,因为...

说明

asp.net core 3.0 一个 jwt 的轻量角色/用户、单个api控制的授权认证库

最近得空,重新做一个角色授权库,而之前做了一个角色授权库,是利用微软的默认接口做的,查阅了很多文档,因为理解不够,所以最终做出了有问题。

之前的旧版本 https://github.com/whuanle/czgl.auth/tree/1.0.0

如果要使用微软的默认接口,我个人认为过于繁杂,而且对于这部分的资料较少。。。

使用默认接口实现授权认证,可以参考我另一篇文章

asp.net core 使用 jwt 自定义角色/策略授权需要实现的接口

得益于大笨熊哥的引导,利用放假时间重新做了一个,利用微软本身的授权认证,在此基础上做拓展。特点是使用十分简便,无需过多配置;因为本身没有“造*”,所以如果需要改造,也十分简单。

此库更新到 .net core 3.0 了,如果需要在 2.2x 上使用,可以到仓库下载项目,然后把 nuget 包换成 2.2 的。

感谢大笨熊哥的指导。

项目仓库地址 https://github.com/whuanle/czgl.auth

一、定义角色、api、用户

随便新建一个网站或api项目,例如 myauth。

nuget 里搜索 czgl.auth,按照 2.0.1 版本,或者使用 package manager 命令

install-package czgl.auth -version 2.0.1

czgl.auth 设计思路是,网站可以存在多个角色、多个用户、多个api,

一个角色拥有一些 api,可以添加或删除角色或修改角色所有权访问的 api;

一个用户可以同时属于几个角色。

第一步要考虑网站的角色、用户、api设计,

czgl.auth 把这些信息存储到内存中,一个用户拥有那几个角色、一个角色具有哪些api的访问权限。

角色跟 api 是对应关系,用户跟角色是多对多关系。

新建一个类 roleservice.cs ,引入 using czgl.auth.services;,roleservice 继承 manarole。

通过以下接口操作角色权限信息

        protected bool addrole(rolemodel role);
        protected bool adduser(usermodel user);
        protected bool removerole(string rolename);
        protected bool removeuser(string username);

很明显,添加/移除一个角色,添加/移除一个用户

假如有 a、b、c 三个角色,
有 /a、/b、/c、/ab、/ac、/bc、/abc 共7个api,设定权限

a 可以访问 a、ab、ac、abc

b 可以访问 b、ab、bc、abc

c 可以访问 c、ac、bc、abc

这里采用模拟数据的方法,不从数据库里面加载实际数据。

在 roleservice 里面增加一个方法

        /// <summary>
        /// 用于加载角色和api
        /// </summary>
        public void updaterole()
        {
            list<rolemodel> roles = new list<rolemodel>
            {
                new rolemodel
                {
                    rolename="a",
                    apis=new list<oneapimodel>
                    {
                        new oneapimodel
                        {
                            apiname="a",
                            apiurl="/a"
                        },
                        new oneapimodel
                        {
                            apiname="ab",
                            apiurl="/ab"
                        },
                        new oneapimodel
                        {
                            apiname="ac",
                            apiurl="/ac"
                        },
                        new oneapimodel
                        {
                            apiname="abc",
                            apiurl="/abc"
                        }
                    }
                },
                new rolemodel
                {
                    rolename="b",
                    apis=new list<oneapimodel>
                    {
                        new oneapimodel
                        {
                            apiname="b",
                            apiurl="/b"
                        },
                        new oneapimodel
                        {
                            apiname="ab",
                            apiurl="/ab"
                        },
                        new oneapimodel
                        {
                            apiname="bc",
                            apiurl="/bc"
                        },
                        new oneapimodel
                        {
                            apiname="abc",
                            apiurl="/abc"
                        }
                    }
                },
                new rolemodel
                {
                    rolename="a",
                    apis=new list<oneapimodel>
                    {
                        new oneapimodel
                        {
                            apiname="a",
                            apiurl="/a"
                        },
                        new oneapimodel
                        {
                            apiname="ab",
                            apiurl="/ab"
                        },
                        new oneapimodel
                        {
                            apiname="ac",
                            apiurl="/ac"
                        },
                        new oneapimodel
                        {
                            apiname="abc",
                            apiurl="/abc"
                        }
                    }
                }
            };
            foreach (var item in roles)
            {
                addrole(item);
            }

        }

有了角色和对应的api信息,就要添加用户了,

假设有 aa、bb、cc 三个用户,密码都是 123456,aa 属于 a 角色, bb 属于 b角色...

        public void updateuser()
        {
            adduser(new usermodel { username = "aa", beroles = new list<string> { "a" } });
            adduser(new usermodel { username = "bb", beroles = new list<string> { "b" } });
            adduser(new usermodel { username = "cc", beroles = new list<string> { "c" } });
        }

为了能够把角色和用户加载进 czgl.auth ,你需要在程序启动时,例如在 program 里,使用

            roleservice roleservice = new roleservice();
            roleservice.updaterole();
            roleservice.updateuser();

二、添加自定义事件

授权是,可能会有各种情况,你可以添加自定义事件记录下用户访问的授权信息、影响授权结果。

引用 using czgl.auth.interface;

添加一个类 roleevents 继承 iroleeventshadner

    public class roleevents : iroleeventshadner
    {
        public async task start(httpcontext httpcontext)
        {
            await task.completedtask;
        }
        public void tokenebnormal(object eventsinfo)
        {
        }
        public void tokenissued(object eventsinfo)
        {
        }
        public void nopermissions(object eventsinfo)
        {
        }
        public void success(object eventsinfo)
        {
        }
        public async task end(httpcontext httpcontext)
        {
            await task.completedtask;
        }
    }

在 czgl.auth 开始验证授权前调用 start,结束时调用 end,传入传参数是 httpcontext 类型,你可以在里面添加自定义授权的信息,在里面可以影响请求管道。

其他几个方法含义如下:

  • tokenebnormal 客户端携带的 token 不是有效的 jwt 令牌,将不能被解析
  • tokenissued 令牌解码后,issuer 或 audience不正确
  • nopermissions 无权访问此 api

在授权认证的各个阶段将会调用上面的方法。

三、注入授权服务和中间件

使用 czgl.auth ,你需要注入以下两个服务

            services.addroleservice(authoptions);
            services.addsingleton<iroleeventshadner, roleevents>();

addroleservice 是注入授权服务,addsingleton 注入你的事件。

addroleservice 需要一个 authconfigmodel 类型作参数。

你可以这样配置

            var authoptions = new authbuilder()
                .security("aaaafsfsfdrhdhrejtrjrt", "aspnetcore", "aspnetcore")
                .jump("accoun/login", "account/error", false, false)
                .time(timespan.fromminutes(20))
                .infoscheme(new czgl.auth.models.authenticatescheme
                {
                    tokenebnormal = "login authentication failed!",
                    tokenissued = "login authentication failed!",
                    nopermissions = "login authentication failed!"
                }).build();
            services.addroleservice(authoptions);

            services.addsingleton<iroleeventshadner, roleevents>();

security 配置密钥相关,参数分别是密钥字符串、颁发者、订阅者。

jump 配置授权失败时,跳转地址。参数分别是未授权时跳转、授权无效跳转,后面两个 bool 可以设置跳转或跳转。

time 配置 token 有效期。

infoscheme 授权失败提示信息,例如

ASP.NET Core 3.0轻量级角色API控制授权库

上图的是时间过期的提示消息,用户请求api失败时返回 401 状态码,header 会携带提示消息,czgl.auth 里面设置了三种情况下,自定义头部:

tokenebnormal 客户端携带的 token 不是有效的 jwt 令牌,将不能被解析

tokenissued 令牌解码后,issuer 或 audience不正确

nopermissions 无权访问此 api

添加三个中间件

            app.useauthentication();
            app.useauthorization();
            app.usemiddleware<rolemiddleware>();

app.useauthorization();是微软授权认证的中间件,czgl.auth 会先让,默认的验证管道过滤一些无效请求和认证信息,再由 czgl.auth 来校验授权。

四、如何设置api的授权

很简单,czgl.auth 的认证授权,你只需在 controller 或 action上 添加 [authorize]

czgl.auth 只会对使用了 [authorize] 特性的 controller 或 action 生效。

如果一个 controller 已经设置了 [authorize] ,但是你想里面的 action 跳过授权认证,则使用 [allowanonymous] 修饰 action。

使用方法跟微软的默认的完全一致。这样无需过多配置。

如果你想另外定义一个特性用来另外设置 授权的话,可以到我的仓库提 issue 或者直接联系我微信。

添加一个 apicontroller ,

    [authorize]
    [route("api/[controller]")]
    [apicontroller]
    public class testcontroller : controllerbase
    {

        [httpget("/a")]
        public jsonresult a()
        {
            return new jsonresult(new { code = 200, message = "success!" });
        }

        [httpget("/b")]
        public jsonresult b()
        {
            return new jsonresult(new { code = 200, message = "success!" });
        }

        [httpget("/c")]
        public jsonresult c()
        {
            return new jsonresult(new { code = 200, message = "success!" });
        }
        [httpget("/ab")]
        public jsonresult ab()
        {
            return new jsonresult(new { code = 200, message = "success!" });
        }
        [httpget("/bc")]
        public jsonresult bc()
        {
            return new jsonresult(new { code = 200, message = "success!" });
        }
        [httpget("/ac")]
        public jsonresult ac()
        {
            return new jsonresult(new { code = 200, message = "success!" });
        }

        [httpget("/abc")]
        public jsonresult abc()
        {
            return new jsonresult(new { claims = user.claims });
        }


        /// <summary>
        /// 任何人都不能访问
        /// </summary>
        /// <returns></returns>
        [httpget("d")]
        public jsonresult d()
        {
            return new jsonresult(new { code = 200, message = "success!" });
        }

        [httpget("error")]
        public jsonresult denied()
        {
            return new jsonresult(
                new
                {
                    code = 0,
                    message = "访问失败!",
                    data = "此账号无权访问!"
                });
        }
    }

五、添加登录颁发 token

添加一个 accountcontroller.cs 用来颁发登录、 token。

    [route("api/[controller]")]
    [apicontroller]
    public class accountcontroller : controllerbase
    {
        [httppost("/login")]
        public async task<jsonresult> login([fromquery]string username, string password, string rolename)
        {
            // 用户名密码是否正确
            if (string.isnullorwhitespace(username) || string.isnullorwhitespace(password) || string.isnullorwhitespace(rolename))
            {
                return new jsonresult(new 
                {
                    code = 0,
                    message = "尼玛,上传什么垃圾信息",
                });
            }

            if(!((username=="aa"||username=="bb"||username=="cc")&&password=="123456"))
            {
                return new jsonresult(new
                {
                    code = 0,
                    message = "账号或密码错误",
                });
            }

            // 你自己定义的角色/用户信息服务
            roleservice roleservice = new roleservice();

            // 检验用户是否属于此角色
            var role = roleservice.isusertorole(username,rolename);

            // czgl.auth 中一个用于加密解密的类
            encryptionhash hash = new encryptionhash();

            // 设置用户标识
            var userclaims = hash.buildclaims(username, rolename);

            //// 自定义构建配置用户标识
            /// 自定义的话,至少包含如下标识
            //var userclaims = new claim[]
            //{
            //new claim(claimtypes.name, username),
            //    new claim(claimtypes.role, rolename),
            //    new claim(jwtregisteredclaimnames.aud, audience),
            //    new claim(claimtypes.expiration, timespan.totalseconds.tostring()),
            //    new claim(jwtregisteredclaimnames.iat, new datetimeoffset(datetime.now).tounixtimeseconds().tostring())
            //};
            /*
            iss (issuer):签发人
            exp (expiration time):过期时间
            sub (subject):主题
            aud (audience):受众
            nbf (not before):生效时间
            iat (issued at):签发时间
            jti (jwt id):编号
            */

            // 方法一,直接颁发 token
            responsetoken token = hash.buildtoken(userclaims);


            //方法二,拆分多步,颁发 token,方便调试
            //var identity = hash.getidentity(userclaims);
            //var jwt = hash.buildjwttoken(userclaims);
            //var token = hash.buildjwtresponsetoken(jwt);

            return new jsonresult(token);
        }
    }

六、部分说明

注入 jwt 服务、颁发 token

czgl.auth 把使用 jwt 的服务和颁发 token 的代码封装好了,这个库不是在“造*”,所以实际上你可以很轻松的把这部分的代码抽出来,另外设计。

这部分的代码所在位置 roleserviceextension.cs 、encryptionhash.cs。

授权中间件

            app.useauthentication();
            app.useauthorization();
            app.usemiddleware<rolemiddleware>();

我的写法是利用 asp.net core 的 jwt 完成基础的认证授权,然后在下一个管道中实现拓展的认证。但是本身的认证是在 app.useauthorization(); 做了拓展,所以使用 czgl.auth,只需要按照平常 jwt 的方式去使用,只是加了一个 rolemiddleware 中间件。

czgl.auth 只是我受到新思路启发临时写出来的。。。最好不要直接用于生产,去 github 库把项目下载下来,按照自己应用场景改一下~。

七、验证

先使用 aa 用户登录,登录时选择 a 角色。

ASP.NET Core 3.0轻量级角色API控制授权库

因为 a 用户只能访问 “带有 a ” 的api, "/a"、"/ab" 等,所以我们可以试试。

ASP.NET Core 3.0轻量级角色API控制授权库

继续用这个 token 访问一下 "/b"

ASP.NET Core 3.0轻量级角色API控制授权库

可以继续尝试添加 api 或者使用其他用户登录,访问不同的 api。

由于别人对前端不熟,所以就不写带页面的示例了~。

可以用 postman 就行测试。

什么示例的 项目可以到仓库里下载,名称是 myauth。

一般上,用户权限、角色权限信息是存储在数据库里面的,另一个示例是 czgl.auth.sample2。

这个库只是较为粗略的授权认证,与更丰富的需求请自行下载源码修改~

到此这篇关于asp.net core 3.0轻量级角色api控制授权库的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持。