首页前端开发HTMLASP.NET Core 使用 JWT 自定义角色/策略授权需要实现的接口

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

时间2023-07-09 14:03:01发布访客分类HTML浏览695
导读:① 存储角色/用户所能访问的 API例如使用 List<ApiPermission> 存储角色的授权 API 列表。可有可无。可以把授权访问的 API 存放到 Token 中,Token 也可以只存放角色信息和用户身份信息。//...

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

例如

使用 ListApiPermission> 存储角色的授权 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.AddSingletonIAuthorizationHandler, PermissionHandler>
    ();
    
            services.AddSingleton(roleRequirement);
    

添加中间件

在微软官网看到例子是这样的。。。但是我测试发现,客户端携带了 Token 信息,请求通过验证上下文,还是失败,这样使用会返回403。

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

发现这样才OK:

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 角色/策略授权,需要继承 AuthorizationHandlerTRequirement>

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

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

/// summary>
    
    /// 验证用户信息,进行权限授权Handler
    /// /summary>
    
    public class PermissionHandler : AuthorizationHandlerPermissionRequirement>

    {

        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                       PermissionRequirement requirement)
        {
    
            ListPermissionRequirement>
     requirements = new ListPermissionRequirement>
    ();

            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);
    

一个逗逗的大学生

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!


若转载请注明出处: ASP.NET Core 使用 JWT 自定义角色/策略授权需要实现的接口
本文地址: https://pptw.com/jishu/298664.html
CZGL.Auth: ASP.NET Core Jwt角色授权快速配置库 数据库内核那些事|为什么我们需要向PolarDB for MySQL 8.0.2演进?

游客 回复需填写必要信息