当前位置: 移动技术网 > IT编程>开发语言>.net > ASP.NET Core 使用 JWT 自定义角色/策略授权需要实现的接口

ASP.NET Core 使用 JWT 自定义角色/策略授权需要实现的接口

2019年08月20日  | 移动技术网IT编程  | 我要评论

wwe酷龙,新华字典词典,音速希希

① 存储角色/用户所能访问的 api

例如

使用 list<apipermission>存储角色的授权 api 列表。

可有可无。

可以把授权访问的 api 存放到 token 中,token 也可以只存放角色信息和用户身份信息。

    /// <summary>
    /// api
    /// </summary>
    public class apipermission
    {
        /// <summary>
        /// api名称
        /// </summary>
        public virtual string name { get; set; }
        /// <summary>
        /// api地址
        /// </summary>
        public virtual string url { get; set; }
    }

② 实现 iauthorizationrequirement 接口

iauthorizationrequirement 接口代表了用户的身份信息,作为认证校验、授权校验使用。

事实上,iauthorizationrequirement 没有任何要实现的内容。

namespace microsoft.aspnetcore.authorization
{
    //
    // 摘要:
    //     represents an authorization requirement.
    public interface iauthorizationrequirement
    {
    }
}

实现 iauthorizationrequirement ,可以任意定义需要的属性,这些会作为自定义验证的便利手段。

    //iauthorizationrequirement 是 microsoft.aspnetcore.authorization 接口

    /// <summary>
    /// 用户认证信息必要参数类
    /// </summary>
    public class permissionrequirement : iauthorizationrequirement
    {
        /// <summary>
        /// 用户所属角色
        /// </summary>
        public role roles { get;  set; } = new role();
        public void setrolesname(string rolename)
        {
            roles.name = rolename;
        }
        /// <summary>
        /// 无权限时跳转到此api
        /// </summary>
        public string deniedaction { get; set; }

        /// <summary>
        /// 认证授权类型
        /// </summary>
        public string claimtype { internal get; set; }
        /// <summary>
        /// 未授权时跳转
        /// </summary>
        public string loginpath { get; set; } = "/account/login";
        /// <summary>
        /// 发行人
        /// </summary>
        public string issuer { get; set; }
        /// <summary>
        /// 订阅人
        /// </summary>
        public string audience { get; set; }
        /// <summary>
        /// 过期时间
        /// </summary>
        public timespan expiration { get; set; }
        /// <summary>
        /// 颁发时间
        /// </summary>
        public long issuedtime { get; set; }
        /// <summary>
        /// 签名验证
        /// </summary>
        public signingcredentials signingcredentials { get; set; }

        /// <summary>
        /// 构造
        /// </summary>
        /// <param name="deniedaction">无权限时跳转到此api</param>
        /// <param name="userpermissions">用户权限集合</param>
        /// <param name="deniedaction">拒约请求的url</param>
        /// <param name="permissions">权限集合</param>
        /// <param name="claimtype">声明类型</param>
        /// <param name="issuer">发行人</param>
        /// <param name="audience">订阅人</param>
        /// <param name="issusedtime">颁发时间</param>
        /// <param name="signingcredentials">签名验证实体</param>
        public permissionrequirement(string deniedaction, role role, string claimtype, string issuer, string audience, signingcredentials signingcredentials,long issusedtime, timespan expiration)
        {
            claimtype = claimtype;
            deniedaction = deniedaction;
            roles = role;
            issuer = issuer;
            audience = audience;
            expiration = expiration;
            issuedtime = issusedtime;
            signingcredentials = signingcredentials;
        }
    }

③ 实现 tokenvalidationparameters

token 的信息配置

        public static tokenvalidationparameters gettokenvalidationparameters()
        {
            var tokenvalida = new tokenvalidationparameters
            {
                // 定义 token 内容
                validateissuersigningkey = true,
                issuersigningkey = new symmetricsecuritykey(encoding.utf8.getbytes(authconfig.securitykey)),
                validateissuer = true,
                validissuer = authconfig.issuer,
                validateaudience = true,
                validaudience = authconfig.audience,
                validatelifetime = true,
                clockskew = timespan.zero,
                requireexpirationtime = true
            };
            return tokenvalida;
        }

④ 生成 token

用于将用户的身份信息(claims)和角色授权信息(permissionrequirement)存放到 token 中。

        /// <summary>
        /// 获取基于jwt的token
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        public static dynamic buildjwttoken(claim[] claims, permissionrequirement permissionrequirement)
        {
            var now = datetime.utcnow;
            var jwt = new jwtsecuritytoken(
                issuer: permissionrequirement.issuer,
                audience: permissionrequirement.audience,
                claims: claims,
                notbefore: now,
                expires: now.add(permissionrequirement.expiration),
                signingcredentials: permissionrequirement.signingcredentials
            );
            var encodedjwt = new jwtsecuritytokenhandler().writetoken(jwt);
            var response = new
            {
                status = true,
                access_token = encodedjwt,
                expires_in = permissionrequirement.expiration.totalmilliseconds,
                token_type = "bearer"
            };
            return response;
        }

⑤ 实现服务注入和身份认证配置

从别的变量导入配置信息,可有可无

            // 设置用于加密 token 的密钥
            // 配置角色权限 
            var rolerequirement = rolepermission.getrolerequirement(accounthash.gettokensecuritykey());

            // 定义如何生成用户的 token
            var tokenvalidationparameters = rolepermission.gettokenvalidationparameters();

配置 asp.net core 的身份认证服务

需要实现三个配置

  • addauthorization 导入角色身份认证策略
  • addauthentication 身份认证类型
  • addjwtbearer jwt 认证配置
            // 导入角色身份认证策略
            services.addauthorization(options =>
            {
                options.addpolicy("permission",
                   policy => policy.requirements.add(rolerequirement));


                // ↓ 身份认证类型
            }).addauthentication(options =>
            {
                options.defaultauthenticatescheme = jwtbearerdefaults.authenticationscheme;
                options.defaultscheme = jwtbearerdefaults.authenticationscheme;
                options.defaultchallengescheme = jwtbearerdefaults.authenticationscheme;

                // ↓ jwt 认证配置
            })
            .addjwtbearer(options =>
            {
                options.tokenvalidationparameters = tokenvalidationparameters;
                options.savetoken = true;
                options.events = new jwtbearerevents()
                {
                    // 在安全令牌通过验证和claimsidentity通过验证之后调用
                    // 如果用户访问注销页面
                    ontokenvalidated = context =>
                    {
                        if (context.request.path.value.tostring() == "/account/logout")
                        {
                            var token = ((context as tokenvalidatedcontext).securitytoken as jwtsecuritytoken).rawdata;
                        }
                        return task.completedtask;
                    }
                };
            });

注入自定义的授权服务 permissionhandler

注入自定义认证模型类 rolerequirement

            // 添加 httpcontext 拦截
            services.addsingleton<iauthorizationhandler, permissionhandler>();

            services.addsingleton(rolerequirement);

添加中间件

貌似这两个不区分先后顺序

            app.useauthorization();
            app.useauthentication();

⑥ 实现登陆

可以在颁发 token 时把能够使用的 api 存储进去,但是这种方法不适合 api 较多的情况。

可以存放 用户信息(claims)和角色信息,后台通过角色信息获取授权访问的 api 列表。

        /// <summary>
        /// 登陆
        /// </summary>
        /// <param name="username">用户名</param>
        /// <param name="password">密码</param>
        /// <returns>token信息</returns>
        [httppost("login")]
        public jsonresult login(string username, string password)
        {
            var user = usermodel.users.firstordefault(x => x.username == username && x.userpossword == password);
            if (user == null)
                return new jsonresult(
                    new responsemodel
                    {
                        code = 0,
                        message = "登陆失败!"
                    });


            // 配置用户标识
            var userclaims = new claim[]
            {
                new claim(claimtypes.name,user.username),
                new claim(claimtypes.role,user.role),
                new claim(claimtypes.expiration,datetime.now.addminutes(_requirement.expiration.totalminutes).tostring()),
            };
            _requirement.setrolesname(user.role);
            // 生成用户标识
            var identity = new claimsidentity(jwtbearerdefaults.authenticationscheme);
            identity.addclaims(userclaims);

            var token = jwttoken.buildjwttoken(userclaims, _requirement);

            return new jsonresult(
                new responsemodel
                {
                    code = 200,
                    message = "登陆成功!请注意保存你的 token 凭证!",
                    data = token
                });
        }

⑦ 添加 api 授权策略

    [authorize(policy = "permission")]

⑧ 实现自定义授权校验

要实现自定义 api 角色/策略授权,需要继承 authorizationhandler<trequirement>

里面的内容是完全自定义的, authorizationhandlercontext 是认证授权的上下文,在此实现自定义的访问授权认证。

也可以加上自动刷新 token 的功能。

    /// <summary>
    /// 验证用户信息,进行权限授权handler
    /// </summary>
    public class permissionhandler : authorizationhandler<permissionrequirement>
    {
        protected override task handlerequirementasync(authorizationhandlercontext context,
                                                       permissionrequirement requirement)
        {
            list<permissionrequirement> requirements = new list<permissionrequirement>();
            foreach (var item in context.requirements)
            {
                requirements.add((permissionrequirement)item);
            }
            foreach (var item in requirements)
            {
                // 校验 颁发和接收对象
                if (!(item.issuer == authconfig.issuer ?
                    item.audience == authconfig.audience ?
                    true : false : false))
                {
                    context.fail();
                }
                // 校验过期时间
                var nowtime = datetimeoffset.now.tounixtimeseconds();
                var issued = item.issuedtime +convert.toint64(item.expiration.totalseconds);
                if (issued < nowtime)
                    context.fail();



                // 是否有访问此 api 的权限
                var resource = ((microsoft.aspnetcore.routing.routeendpoint)context.resource).routepattern;
                var permissions = item.roles.permissions.tolist();
                var apis = permissions.any(x => x.name.tolower() == item.roles.name.tolower() && x.url.tolower() == resource.rawtext.tolower());
                if (!apis)
                    context.fail();

                context.succeed(requirement);
                // 无权限时跳转到某个页面
                //var httpcontext = new httpcontextaccessor();
                //httpcontext.httpcontext.response.redirect(item.deniedaction);
            }

            context.succeed(requirement);
            return task.completedtask;
        }
    }

⑨ 一些有用的代码

将字符串生成哈希值,例如密码。

为了安全,删除字符串里面的特殊字符,例如 "'$

    public static class accounthash
    {

        // 获取字符串的哈希值
        public static string getbyhashstring(string str)
        {
            string hash = getmd5hash(str.replace("\"", string.empty)
                .replace("\'", string.empty)
                .replace("$", string.empty));
            return hash;
        }
        /// <summary>
        /// 获取用于加密 token 的密钥
        /// </summary>
        /// <returns></returns>
        public static signingcredentials gettokensecuritykey()
        {
            var securitykey = new signingcredentials(
                new symmetricsecuritykey(
                    encoding.utf8.getbytes(authconfig.securitykey)), securityalgorithms.hmacsha256);
            return securitykey;
        }
        private static string getmd5hash(string source)
        {
            md5 md5hash = md5.create();
            byte[] data = md5hash.computehash(encoding.utf8.getbytes(source));
            stringbuilder sbuilder = new stringbuilder();
            for (int i = 0; i < data.length; i++)
            {
                sbuilder.append(data[i].tostring("x2"));
            }
            return sbuilder.tostring();
        }
    }

签发 token

permissionrequirement 不是必须的,用来存放角色或策略认证信息,claims 应该是必须的。

    /// <summary>
    /// 颁发用户token
    /// </summary>
    public class jwttoken
    {
        /// <summary>
        /// 获取基于jwt的token
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        public static dynamic buildjwttoken(claim[] claims, permissionrequirement permissionrequirement)
        {
            var now = datetime.utcnow;
            var jwt = new jwtsecuritytoken(
                issuer: permissionrequirement.issuer,
                audience: permissionrequirement.audience,
                claims: claims,
                notbefore: now,
                expires: now.add(permissionrequirement.expiration),
                signingcredentials: permissionrequirement.signingcredentials
            );
            var encodedjwt = new jwtsecuritytokenhandler().writetoken(jwt);
            var response = new
            {
                status = true,
                access_token = encodedjwt,
                expires_in = permissionrequirement.expiration.totalmilliseconds,
                token_type = "bearer"
            };
            return response;
        }

表示时间戳

// unix 时间戳
datetimeoffset.now.tounixtimeseconds();

// 检验 token 是否过期
// 将 timespan 转为 unix 时间戳
convert.toint64(timespan);
datetimeoffset.now.tounixtimeseconds() + convert.toint64(timespan);

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网