① 存储角色/用户所能访问的 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. SQL手工注入进阶篇

    0.前言 上一篇我们介绍了SQL手工注入的流程以及步骤,但在实际的安全问题以及CTF题目中,查询语句多种多样,而且是肯定会对用户的输入进行一个安全过滤的,而这些过滤并不一定是百分百安全的,如何利用一些 ...

  2. Redis高级客户端Lettuce详解

    前提 Lettuce是一个Redis的Java驱动包,初识她的时候是使用RedisTemplate的时候遇到点问题Debug到底层的一些源码,发现spring-data-redis的驱动包在某个版本之 ...

  3. 【面试题】Java常见面试题

    集合与数组? 数组:(可以存储基本数据类型)是用来存储对象的一种容器,但是数组的长度固定,不适合在对象数量未知的情况下使用 集合:(只能存储对象,对象类型可以不一样)集合的长度可变,可在多数情况下使用 ...

  4. Spring MVC 梳理 - handlerMapping和handlerAdapter分析

    参考图片 综上所述我们来猜测一下spring mvc 中根据URL找到处理器Controller中相应方法的流程 ①:获取Request的URL ②:从UrlLookup这个map中找到相应的requ ...

  5. jquery 动态控制显隐

    1.第1种方法 ,给元素设置style属性 $("#hidediv").css("display", "block"); 2.第2种方法 , ...

  6. java实现图片验证码

    一.验证码生成类 package hbi.tech.utils; import javax.imageio.ImageIO; import java.awt.*; import java.awt.im ...

  7. 一条SQL查询语句是如何执行的?

    本篇文章将通过一条 SQL 的执行过程来介绍 MySQL 的基础架构. 首先有一个 user_info 表,表里有一个 id 字段,执行下面这条查询语句: select * from user_inf ...

  8. 如何编写高质量的 JS 函数(3) --函数式编程[理论篇]

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/EWSqZuujHIRyx8Eb2SSidQ作者:杨昆 [编写高质量函数系列]中, <如何 ...

  9. WCF客户端简单动态配置服务地址

    本来想实现WCF服务无论放到哪个机器上,我的客户端都不需要重新编译,只需要配置一个服务的地址即可.各种百度找到了很多解决方案.但都比较繁琐,(只要因为个人小菜看不懂太多的代码)我对WCF内部机制还不了 ...

  10. 手把手教你如何在window下将jenkins+allure集成生成的测试报告通过jenkins配置邮箱自动发送-04(非常详细,非常实用)

    简介 上一篇生成测试报告,小伙伴们和童鞋们就又问道,测试报告已经生成了,怎么发送给相关的负责人了?小伙伴们和童鞋们不要着急,听宏哥慢慢给你道来,心急吃不了热豆腐哈.今天这篇文章宏哥就给小伙伴和童鞋们来 ...