ASP.NET Core 2.2 : 二十七. JWT与用户授权(细化到Action)

上一章分享了如何在asp.net core中应用jwt进行用户认证以及token的刷新,本章继续进行下一步,用户授权。涉及到的例子也以上一章的为基础。(asp.net core 系列目录

一、概述

首先说一下认证(authentication)与授权(authorization),它们经常在一起工作,所以有时候会分不清楚。并且这两个英文单词长得也像兄弟。举例来说,我刷门禁卡进入公司,门禁【认证】了我是这里的员工,可以进入;但进入公司以后,我并不是所有房间都可以进,比如“机房重地,闲人免进”,我能进入哪些房间,需要公司的【授权】。这就是认证和授权的区别。

asp.net core提倡的是基于声明(claim)的授权,关于这个claim,上一章用到过,有如下这样的代码,但没有介绍:

claim[] claims = new claim[] { new claim(claimtypes.nameidentifier, user.code), new claim(claimtypes.name, user.name) };

这是一个声明的集合,它包含了两个 声明,用于保存了用户的唯一id和用户名。当然我们还可以添加更多的claim。对应claim,还有claimsidentity 和claimsprincipal 两个类型。

claimsidentity相当于是一个证件,例如上例的门禁卡;claimsprincipal 则是证件的持有者,也就是我本人;那么对应的claim就是门禁卡内存储的一些信息,例如证件号、持有人姓名等。

我除了门禁卡还有身份证、银行卡等,也就是说一个claimsprincipal中可以有多个claimsidentity,而一个claimsidentity中可以有多个claim。asp.net core的授权模型大概就是这样的一个体系。

asp.net core支持多种授权方式,包括兼容之前的角色授权。下面通过几个例子说明一下(例子依然以上一章的代码为基础)。

二、基于角色授权

asp.net core兼容之前的角色授权模式,如何使用呢?由于不是本文的重点,这里只是简要说一下。修改flylolo.jwt.server的tokenhelper临时为张三添加了一个名为“testputbookrole”的权限(实际权限来源此处不做展示)。

        public complextoken createtoken(user user)
        {
            claim[] claims = new claim[] { new claim(claimtypes.nameidentifier, user.code), new claim(claimtypes.name, user.name) };

            //下面对code为001的张三添加了一个claim,用于测试在token中存储用户的角色信息,对应测试在flylolo.jwt.api的bookcontroller的put方法,若用不到可删除
            if (user.code.equals("001"))
            {
                claims = claims.append(new claim(claimtypes.role, "testputbookrole")).toarray();
            }
            
            return createtoken(claims);
        }

修改flylolo.jwt.api的bookcontroller,添加了一个action如下

        /// <summary>
        /// 测试在jwt的token中添加角色,在此验证  见tokenhelper
        /// </summary>
        /// <returns></returns>
        [httpput]
        [authorize(roles = "testputbookrole")]
        public jsonresult put()
        {
            return new jsonresult("put  book ...");
        }

访问这个action,只有用张三登录后获取的token能正常访问。

三、基于声明授权

对于上例来说,本质上也是基于声明(claim)的授权,因为张三的"testputbookrole"角色也是作为一个claim添加到证书中的。只不过采用了特定的claimtypes.role。那么是否可以将其他的普通claim作为授权的依据呢?当然是可以的。

这里涉及到了另一个单词“policy”,翻译为策略?也就是说,可以把一系列的规则(例如要求姓名为李四,账号为002,国籍为中国等等)组合在一起,形成一个policy,只有满足这个policy的才可以被授权访问。

下面我们就新建一个policy,在startup的configureservices中添加授权代码:

services.addauthorization(options=>options.addpolicy("name",policy=> {
   policy.requireclaim(claimtypes.name, "张三");
   policy.requireclaim(claimtypes.nameidentifier,"001");
}));

在bookcontroller中添加一个action如下

[httpdelete]
[authorize(policy = "testpolicy")]
public jsonresult delete()
{
    return new jsonresult("delete book ...");
}

可以通过张三和李四的账号测试一下,只有使用张三的账号获取的token能访问成功。

四、基于策略自定义授权

上面介绍了两种授权方式,现在有个疑问,通过角色授权,只适合一些小型项目,将几个功能通过角色区分开就可以了。

通过声明的方式,目测实际项目中需要在startup中先声明一系列的policy,然后在controller或action中使用。

这两种方式都感觉不好。例如经常存在这样的需求:一个用户可以有多个角色,每个角色对应多个可访问的api地址(将授权细化到具体的action)。用户还可以被特殊的授予某个api地址的权限。

这样的需求采用上面的两种方式实现起来都很麻烦,好在asp.net core提供了方便的扩展方式。

1.样例数据

将上面的需求汇总一下,最终可以形成如下形式的数据:

/// <summary>
/// 虚拟数据,模拟从数据库或缓存中读取用户相关的权限
/// </summary>
public static class temporarydata
{
    public readonly static list<userpermissions> userpermissions = new list<userpermissions> {
        new userpermissions {
            code = "001",
            permissions = new list<permission> {
                new permission { code = "a1", name = "student.create", url = "/api/student",method="post" },
                new permission { code = "a2", name = "student.delete", url = "/api/student",method="delete"}
            }
        },
        new userpermissions {
            code = "002",
            permissions = new list<permission> {
                new permission { code = "b1", name = "book.create", url = "/api/book" ,method="post"},
                new permission { code = "b2", name = "book.delete", url = "/api/book" ,method="delete"}
            }
        },
    };

    public static userpermissions getuserpermission(string code)
    {
        return userpermissions.firstordefault(m => m.code.equals(code));
    }
}

涉及到的两个类如下:

    public class permission
    {
        public string code { get; set; }
        public string name { get; set; }
        public string url { get; set; }

        public string method { get; set; }
    }

    public class userpermissions
    {
        public string code { get; set; }
        public list<permission> permissions { get; set; }
    }

 

2.自定义处理程序

下面就是根据样例数据来制定相应的处理程序了。这涉及到iauthorizationrequirement和authorizationhandler两个内容。

iauthorizationrequirement是一个空的接口,主要用于提供授权所需要满足的“要求”,或者说是“规则”。authorizationhandler则是对请求和“要求”的联合处理。

新建一个permissionrequirement实现iauthorizationrequirement接口。

public class permissionrequirement: iauthorizationrequirement
{
    public list<userpermissions> usepermissionlist { get { return temporarydata.userpermissions; } }
}

很简单的内容。它的“要求”也就是用户的权限列表了,用户的权限列表中包含当前访问的api,则授权通过,否则不通过。

判断逻辑放在新建的permissionhandler中:

public class permissionhandler : authorizationhandler<permissionrequirement>
{
    protected override task handlerequirementasync(authorizationhandlercontext context, permissionrequirement requirement)
    {
        var code = context.user.claims.firstordefault(m => m.type.equals(claimtypes.nameidentifier));
        if (null != code)
        {
            userpermissions userpermissions = requirement.usepermissionlist.firstordefault(m => m.code.equals(code.value.tostring()));

            var request = (context.resource as authorizationfiltercontext).httpcontext.request;

            if (null != userpermissions && userpermissions.permissions.any(m => m.url.tolower().equals(request.path.value.tolower()) && m.method.tolower().equals(request.method.tolower()) ))
            {
                context.succeed(requirement);
            }
            else
            {
                context.fail();
            }
        }
        else
        {
            context.fail();
        }

        return task.completedtask;
    }
}

逻辑很简单不再描述。

3.使用自定义的处理程序

在startup的configureservices中添加授权代码

services.addauthorization(options => options.addpolicy("permission", policy => policy.requirements.add(new permissionrequirement())));
services.addsingleton<iauthorizationhandler, permissionhandler>();

将bookcontroller的delete action修改一下:

[httpdelete]
//[authorize(policy = "testpolicy")]
[authorize(policy = "permission")]
public jsonresult delete()
{
    return new jsonresult("delete book ...");
}

测试一下只有李四可以访问这个action。

 

代码地址:https://github.com/flylolo/jwt.demo/releases/tag/2.0

本文链接:https://2i3i.com/aspnetcore2_27.html ,转载请注明来源地址。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇