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

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

  1. asp.net core 集成JWT(二)token的强制失效,基于策略模式细化api权限

    [前言] 上一篇我们介绍了什么是JWT,以及如何在asp.net core api项目中集成JWT权限认证.传送门:https://www.cnblogs.com/7tiny/p/11012035.h ...

  2. asp.net core 3.1 自定义中间件实现jwt token认证

    asp.net core 3.1 自定义中间件实现jwt token认证 话不多讲,也不知道咋讲!直接上代码 认证信息承载对象[user] /// <summary> /// 认证用户信息 ...

  3. asp.net core 集成JWT(一)

    [什么是JWT] JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案. JWT的官网地址:https://jwt.io/ 通俗地来讲,JWT是能代表用户身份的令牌,可以使用JWT ...

  4. ASP.NET Core 基于JWT的认证(二)

    ASP.NET Core 基于JWT的认证(二) 上一节我们对 Jwt 的一些基础知识进行了一个简单的介绍,这一节我们将详细的讲解,本次我们将详细的介绍一下 Jwt在 .Net Core 上的实际运用 ...

  5. ASP.NET Core 基于JWT的认证(一)

    ASP.NET Core 基于JWT的认证(一) Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计 ...

  6. ASP.NET Core中显示自定义错误页面-增强版

    之前的博文 ASP.NET Core中显示自定义错误页面 中的方法是在项目中硬编码实现的,当有多个项目时,就会造成不同项目之间的重复代码,不可取. 在这篇博文中改用middleware实现,并且放在独 ...

  7. Asp.Net Core基于JWT认证的数据接口网关Demo

    近日,应一位朋友的邀请写了个Asp.Net Core基于JWT认证的数据接口网关Demo.朋友自己开了个公司,接到的一个升级项目,客户要求用Aps.Net Core做数据网关服务且基于JWT认证实现对 ...

  8. 在ASP.NET Core上实施每个租户策略的数据库

    在ASP.NET Core上实施每个租户策略的数据库 不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址 本博文翻译自: http://g ...

  9. WebAPI调用笔记 ASP.NET CORE 学习之自定义异常处理 MySQL数据库查询优化建议 .NET操作XML文件之泛型集合的序列化与反序列化 Asp.Net Core 轻松学-多线程之Task快速上手 Asp.Net Core 轻松学-多线程之Task(补充)

    WebAPI调用笔记   前言 即时通信项目中初次调用OA接口遇到了一些问题,因为本人从业后几乎一直做CS端项目,一个简单的WebAPI调用居然浪费了不少时间,特此记录. 接口描述 首先说明一下,基于 ...

随机推荐

  1. 【全网首创】修改 Ext.ux.UploadDialog.Dialog 源码支持多选添加文件,批量上传文件

    公司老框架的一个页面需要用到文件上传,本以为修改一个配置参数即可解决,百度一番发现都在说这个第三方插件不支持文件多选功能,还有各种各样缺点,暂且不讨论这些吧.先完成领导安排下来的任务. 任务一:支持多 ...

  2. java数据结构——队列、循环队列(Queue)

    每天进步一点点,坚持就是成功. 1.队列 /** * 人无完人,如有bug,还请斧正 * 继续学习Java数据结构————队列(列队) * 队列和栈一样,都是使用数组,但是队列多了一个队头,队头访问数 ...

  3. 在Docker中启动Cloudera

    写在前面 记录一下,一个简单的cloudera处理平台的构建过程和一些基本组件的使用 前置说明 需要一台安装有Docker的机器 docker常用命令: docker ps docker ps -a ...

  4. django查询表记录的十三种方法

    django查询表记录的十三种方法 all() 结果为queryset类型 >>> models.Book.objects.all() <QuerySet [<Book: ...

  5. Javascript的基础

    ECMAScript(语法.标准) BOM(浏览器) DOM(网页) ECMAScript是一个标准,它规定了语法.类型.语句.关键字.保留子.操作符.对象.(相当于法律) BOM(浏览器对象模型): ...

  6. Python学习笔记整理总结【Django】:模板语言、分页、Cookie、Session

    一.模板语言  1.在前段展示:对象/字典/元组 class Business(models.Model): # id #用默认的自增id列 即:Business中有3列数据(id, caption, ...

  7. Git基础概念与Flow流程介绍

    目录 Git相关 基本概念 常见客户端 TortoiseGit Sourcetree Intellij Idea 命令行 常用命令 存储区域 命令之 add & commit &pus ...

  8. CentOS系统查看软件安装路径

    Linux系统一般都是命令行界面,对于安装的软件也是通过命令安装的.对于软件包更新和卸载等有时候需要查看检查是否有改软件,软件安装存储的路径对于修改配置文件等是必要的.那么怎么查看软件安装路径呢?小编 ...

  9. 地图的折线:Polyline

    (1)var polyline = new BMap.Polyline([new BMap.Point(X1,Y1),new BMap.Point(X2,Y2),new BMap.Point(X3,Y ...

  10. Inkscape 旋转并复制

    画一个图形,点击图标. 然后图标中心有个十字叉, 然后把这个十字叉拖到你想要旋转的地方. 然后shift+ctrl+m打开变换菜单. 选择旋转选项卡,然后设置角度,点击应用.就可以旋转了,如果配合ct ...