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

目录

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

  • ② 实现 IAuthorizationRequirement 接口

  • ③ 实现 TokenValidationParameters

  • ④ 生成 Token

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

  • ⑥ 实现登陆

  • ⑦ 添加 API 授权策略

  • ⑧ 实现自定义授权校验

  • ⑨ 一些有用的代码

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

例如 使用 List<ApiPermission>存储角色的授权 API 列表。

可有可无。

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

    /// &lt;summary&gt;
    /// API
    /// &lt;/summary&gt;
    public class ApiPermission
    {
        /// &lt;summary&gt;
        /// API名称
        /// &lt;/summary&gt;
        public virtual string Name { get; set; }
        /// &lt;summary&gt;
        /// API地址
        /// &lt;/summary&gt;
        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 接口

    /// &lt;summary&gt;
    /// 用户认证信息必要参数类
    /// &lt;/summary&gt;
    public class PermissionRequirement : IAuthorizationRequirement
    {
        /// &lt;summary&gt;
        /// 用户所属角色
        /// &lt;/summary&gt;
        public Role Roles { get;  set; } = new Role();
        public void SetRolesName(string roleName)
        {
            Roles.Name = roleName;
        }
        /// &lt;summary&gt;
        /// 无权限时跳转到此API
        /// &lt;/summary&gt;
        public string DeniedAction { get; set; }

        /// &lt;summary&gt;
        /// 认证授权类型
        /// &lt;/summary&gt;
        public string ClaimType { internal get; set; }
        /// &lt;summary&gt;
        /// 未授权时跳转
        /// &lt;/summary&gt;
        public string LoginPath { get; set; } = "/Account/Login";
        /// &lt;summary&gt;
        /// 发行人
        /// &lt;/summary&gt;
        public string Issuer { get; set; }
        /// &lt;summary&gt;
        /// 订阅人
        /// &lt;/summary&gt;
        public string Audience { get; set; }
        /// &lt;summary&gt;
        /// 过期时间
        /// &lt;/summary&gt;
        public TimeSpan Expiration { get; set; }
        /// &lt;summary&gt;
        /// 颁发时间
        /// &lt;/summary&gt;
        public long IssuedTime { get; set; }
        /// &lt;summary&gt;
        /// 签名验证
        /// &lt;/summary&gt;
        public SigningCredentials SigningCredentials { get; set; }

        /// &lt;summary&gt;
        /// 构造
        /// &lt;/summary&gt;
        /// &lt;param name="deniedAction"&gt;无权限时跳转到此API&lt;/param&gt;
        /// &lt;param name="userPermissions"&gt;用户权限集合&lt;/param&gt;
        /// &lt;param name="deniedAction"&gt;拒约请求的url&lt;/param&gt;
        /// &lt;param name="permissions"&gt;权限集合&lt;/param&gt;
        /// &lt;param name="claimType"&gt;声明类型&lt;/param&gt;
        /// &lt;param name="issuer"&gt;发行人&lt;/param&gt;
        /// &lt;param name="audience"&gt;订阅人&lt;/param&gt;
        /// &lt;param name="issusedTime"&gt;颁发时间&lt;/param&gt;
        /// &lt;param name="signingCredentials"&gt;签名验证实体&lt;/param&gt;
        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 中。

        /// &lt;summary&gt;
        /// 获取基于JWT的Token
        /// &lt;/summary&gt;
        /// &lt;param name="username"&gt;&lt;/param&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        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 =&gt;
            {
                options.AddPolicy("Permission",
                   policy =&gt; policy.Requirements.Add(roleRequirement));


                // ↓ 身份认证类型
            }).AddAuthentication(options =&gt;
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

                // ↓ Jwt 认证配置
            })
            .AddJwtBearer(options =&gt;
            {
                options.TokenValidationParameters = tokenValidationParameters;
                options.SaveToken = true;
                options.Events = new JwtBearerEvents()
                {
                    // 在安全令牌通过验证和ClaimsIdentity通过验证之后调用
                    // 如果用户访问注销页面
                    OnTokenValidated = context =&gt;
                    {
                        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&lt;IAuthorizationHandler, PermissionHandler&gt;();

            services.AddSingleton(roleRequirement);

添加中间件

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

            app.UseAuthorization();
            app.UseAuthentication();

⑥ 实现登陆

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

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

        /// &lt;summary&gt;
        /// 登陆
        /// &lt;/summary&gt;
        /// &lt;param name="username"&gt;用户名&lt;/param&gt;
        /// &lt;param name="password"&gt;密码&lt;/param&gt;
        /// &lt;returns&gt;Token信息&lt;/returns&gt;
        [HttpPost("login")]
        public JsonResult Login(string username, string password)
        {
            var user = UserModel.Users.FirstOrDefault(x =&gt; x.UserName == username &amp;&amp; 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 的功能。

    /// &lt;summary&gt;
    /// 验证用户信息,进行权限授权Handler
    /// &lt;/summary&gt;
    public class PermissionHandler : AuthorizationHandler&lt;PermissionRequirement&gt;
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                       PermissionRequirement requirement)
        {
            List&lt;PermissionRequirement&gt; requirements = new List&lt;PermissionRequirement&gt;();
            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 &lt; nowTime)
                    context.Fail();



                // 是否有访问此 API 的权限
                var resource = ((Microsoft.AspNetCore.Routing.RouteEndpoint)context.Resource).RoutePattern;
                var permissions = item.Roles.Permissions.ToList();
                var apis = permissions.Any(x =&gt; x.Name.ToLower() == item.Roles.Name.ToLower() &amp;&amp; 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;
        }
        /// &lt;summary&gt;
        /// 获取用于加密 Token 的密钥
        /// &lt;/summary&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        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 &lt; data.Length; i++)
            {
                sBuilder.Append(data[i].ToString("x2"));
            }
            return sBuilder.ToString();
        }
    }

签发 Token

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

    /// &lt;summary&gt;
    /// 颁发用户Token
    /// &lt;/summary&gt;
    public class JwtToken
    {
        /// &lt;summary&gt;
        /// 获取基于JWT的Token
        /// &lt;/summary&gt;
        /// &lt;param name="username"&gt;&lt;/param&gt;
        /// &lt;returns&gt;&lt;/returns&gt;
        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;
        }

表示时间戳

 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值