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

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

① 存储角色/用户所能访问的 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);


添加中间件

在微软官网看到例子是这样的。。。但是我测试发现,客户端携带了 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 角色/策略授权,需要继承 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);
相关文章
|
10天前
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
32 5
|
2月前
|
存储 开发框架 JSON
ASP.NET Core OData 9 正式发布
【10月更文挑战第8天】Microsoft 在 2024 年 8 月 30 日宣布推出 ASP.NET Core OData 9,此版本与 .NET 8 的 OData 库保持一致,改进了数据编码以符合 OData 规范,并放弃了对旧版 .NET Framework 的支持,仅支持 .NET 8 及更高版本。新版本引入了更快的 JSON 编写器 `System.Text.UTF8JsonWriter`,优化了内存使用和序列化速度。
|
28天前
|
开发框架 .NET C#
在 ASP.NET Core 中创建 gRPC 客户端和服务器
本文介绍了如何使用 gRPC 框架搭建一个简单的“Hello World”示例。首先创建了一个名为 GrpcDemo 的解决方案,其中包含一个 gRPC 服务端项目 GrpcServer 和一个客户端项目 GrpcClient。服务端通过定义 `greeter.proto` 文件中的服务和消息类型,实现了一个简单的问候服务 `GreeterService`。客户端则通过 gRPC 客户端库连接到服务端并调用其 `SayHello` 方法,展示了 gRPC 在 C# 中的基本使用方法。
38 5
在 ASP.NET Core 中创建 gRPC 客户端和服务器
|
18天前
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
24 3
|
2月前
|
存储 安全 Java
|
3月前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
3月前
|
开发框架 .NET 中间件
ASP.NET Core Web 开发浅谈
本文介绍ASP.NET Core,一个轻量级、开源的跨平台框架,专为构建高性能Web应用设计。通过简单步骤,你将学会创建首个Web应用。文章还深入探讨了路由配置、依赖注入及安全性配置等常见问题,并提供了实用示例代码以助于理解与避免错误,帮助开发者更好地掌握ASP.NET Core的核心概念。
111 3
|
2月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
3月前
|
开发框架 NoSQL .NET
利用分布式锁在ASP.NET Core中实现防抖
【9月更文挑战第5天】在 ASP.NET Core 中,可通过分布式锁实现防抖功能,仅处理连续相同请求中的首个请求,其余请求返回 204 No Content,直至锁释放。具体步骤包括:安装分布式锁库如 `StackExchange.Redis`;创建分布式锁服务接口及其实现;构建防抖中间件;并在 `Startup.cs` 中注册相关服务和中间件。这一机制有效避免了短时间内重复操作的问题。
|
4月前
|
开发框架 监控 .NET
开发者的革新利器:ASP.NET Core实战指南,构建未来Web应用的高效之道
【8月更文挑战第28天】本文探讨了如何利用ASP.NET Core构建高效、可扩展的Web应用。ASP.NET Core是一个开源、跨平台的框架,具有依赖注入、配置管理等特性。文章详细介绍了项目结构规划、依赖注入配置、中间件使用及性能优化方法,并讨论了安全性、可扩展性以及容器化的重要性。通过这些技术要点,开发者能够快速构建出符合现代Web应用需求的应用程序。
70 0