首页 > 编程语言 >Asp.Net Core 集成JWT采用Rsa非对称密钥并实现自定义身份验证

Asp.Net Core 集成JWT采用Rsa非对称密钥并实现自定义身份验证

时间:2023-07-28 17:44:49浏览次数:45  
标签:Core 自定义 get 身份验证 var new httpContext true public

授权和鉴权分为了两个项目。

首先是授权:

建立Asp.net core 项目,并在Nuget包安装 System.IdentityModel.Tokens.Jwt
新建一个Web Api 用于登录,这里使用账户密码方便调试。另外BaseResult是我封装的一个统一返回数据类型。需要注意的是 audience 以及 JwtRegisteredClaimNames.Name 都不应该固定写死(如果有三方或多客户端登录等...),我这里只是方便调试。具体请到Jwt查看相关资料。
/// <summary>
/// 账户密码登录
/// </summary>
/// <param name="loginModel"></param>
/// <returns></returns>
[HttpPost]
public BaseResult Post(LoginModel loginModel)
{
//检查账户密码
Users user = usersManage.GetUserByAccountAndPwd(loginModel.AccountName, loginModel.Password);

if (user == null)
{
return new BaseResult { result = "error", code = E_ResultCode.error, message = "账号名或密码错误" };
}


var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Name, "ZxManagementPlatform"),
new Claim("ID",user.ID)
//此处可以自定义添加所需要的数据...
};

claims.AddRange(roles.Select(x => new Claim("Role", x.RoleCode)));

//=====================================================================
//对称
//var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(GlobalConfig.JwtSecurityKey));

//=====================================================================
//非对称

var rsa = RSA.Create();
byte[] publicKey = Convert.FromBase64String("公钥Base64(掐头去尾,不带Begin...以及回车空格等格式的) ---这里其实不需要公钥,但是可以作为保存");
byte[] privateKey = Convert.FromBase64String("私钥Base64(掐头去尾,不带Begin...以及回车空格等格式的) ---我是用的是 Pkcs8格式的密钥");

rsa.ImportPkcs8PrivateKey(privateKey, out _);

var key = new RsaSecurityKey(rsa);
PrivateKeyStatus privateKeyStatus = key.PrivateKeyStatus;

//=====================================================================

var token = new JwtSecurityToken(
issuer: GlobalConfig.JwtIssuer,
audience: "zxservice",
claims: claims,
notBefore: DateTime.Now,
expires: DateTime.Now.AddHours(2),
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.RsaSha256)
//signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
);

var jwtToken = new JwtSecurityTokenHandler().WriteToken(token);

return new BaseResult { result = "success", code = E_ResultCode.success, message = "成功", data = jwtToken };
}

然后是鉴权

建立Asp.net core 项目,并在Nuget包安装 Microsoft.AspNetCore.Authentication.JwtBearer
添加自定义身份校验代码
这个部分参考于 https://blog.csdn.net/weixin_33854644/article/details/85018389 ,并修改了一部分。
首先建立文件夹JwtPolicy 并添加文件和代码
//==================================================================================
//============================ 文件一

/// <summary>
/// 用户或角色或其他凭据实体
/// </summary>
public class Permission
{
/// <summary>
/// 用户或角色或其他凭据名称
/// </summary>
public virtual string Name { get; set; }
/// <summary>
/// 请求Url
/// </summary>
public virtual string Url { get; set; }
}

//==================================================================================
//============================ 文件二

/// <summary>
/// 权限授权Handler
/// </summary>
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
/// <summary>
/// 验证方案提供对象
/// </summary>
public IAuthenticationSchemeProvider Schemes { get; set; }

/// <summary>
/// 自定义策略参数
/// </summary>
public PermissionRequirement Requirement { get; set; }

/// <summary>
/// 没有权限,禁止访问
/// </summary>
private static readonly string ForbiddenResult = JsonConvert.SerializeObject(new BaseResult { code = E_ResultCode.Forbidden, result = "error", message = "没有权限,禁止访问" });

/// <summary>
/// 没有登录
/// </summary>
private static readonly string UnauthorizedResult = JsonConvert.SerializeObject(new BaseResult { code = E_ResultCode.Unauthorized, result = "error", message = "请先登录" });

/// <summary>
/// 构造
/// </summary>
/// <param name="schemes"></param>
public PermissionHandler(IAuthenticationSchemeProvider schemes)
{
Schemes = schemes;
}

/// <summary>
/// 权限校验
/// </summary>
/// <param name="context"></param>
/// <param name="requirement"></param>
/// <returns></returns>
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{

//赋值用户权限
Requirement = requirement;
//从AuthorizationHandlerContext转成HttpContext,以便取出表求信息
var httpContext = (context.Resource as Microsoft.AspNetCore.Http.DefaultHttpContext).HttpContext;


//====================================================================================

//请求Url
var questUrl = httpContext.Request.Path.Value.ToLower();
//请求的方式
var questMethod = httpContext.Request.Method.ToLower();

//判断请求是否停止
var handlers = httpContext.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();
foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())
{
var handler = await handlers.GetHandlerAsync(httpContext, scheme.Name) as IAuthenticationRequestHandler;
if (handler != null && await handler.HandleRequestAsync())
{
context.Fail();
return;
}
}
//判断请求是否拥有凭据,即有没有登录
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var result = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);
//result?.Principal不为空 并且 Succeeded == true 即登录成功
if (result?.Succeeded == true && result?.Principal != null)
{
httpContext.User = result.Principal;
//权限中是否存在请求的url
if (Requirement.Permissions.Where(x => questUrl.ToLower().StartsWith(x.Url.ToLower())).Any())
{
List<string> LtRoles = httpContext.User.Claims.Where(s => s.Type == requirement.ClaimType).Select(x => x.Value).ToList();

//验证权限
if (!Requirement.Permissions.Where(w => LtRoles.Contains(w.Name) && questUrl.ToLower().StartsWith(w.Url.ToLower()) && w.Method.ToLower() == questMethod).Any())
{
//无权限拒绝访问
httpContext.Response.ContentType = "application/json";
await httpContext.Response.WriteAsync(ForbiddenResult);
context.Fail();
}

}
context.Succeed(requirement);
return;
}
else
{
//没有登录
httpContext.Response.ContentType = "application/json";
await httpContext.Response.WriteAsync(UnauthorizedResult);
context.Fail();
}
}

//判断没有登录时,是否访问登录的url,并且是Post请求,并助是form表单提交类型,否则为失败
if (!questUrl.Equals(Requirement.LoginPath.ToLower(), StringComparison.Ordinal) && (!httpContext.Request.Method.Equals("POST") || !httpContext.Request.HasFormContentType))
{
//没有登录
httpContext.Response.ContentType = "application/json";
await httpContext.Response.WriteAsync(UnauthorizedResult);
context.Fail();
}

context.Succeed(requirement);
}
}
//==================================================================================
//============================ 文件三

/// <summary>
/// 必要参数类
/// </summary>
public class PermissionRequirement : IAuthorizationRequirement
{
/// <summary>
/// 用户权限集合
/// </summary>
public List<Permission> Permissions { get; private set; }
/// <summary>
/// 无权限action
/// </summary>
public string DeniedAction { get; set; }
/// <summary>
/// 认证授权类型
/// </summary>
public string ClaimType { internal get; set; }
/// <summary>
/// 请求路径
/// </summary>
public string LoginPath { get; set; } = "/Api/Login";
/// <summary>
/// 发行人
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// 订阅人
/// </summary>
public string Audience { get; set; }
/// <summary>
/// 过期时间
/// </summary>
public TimeSpan Expiration { get; set; } = TimeSpan.FromMinutes(5000);
/// <summary>
/// 签名验证
/// </summary>
public SigningCredentials SigningCredentials { get; set; }

/// 构造
/// </summary>
/// <param name="deniedAction">拒约请求的url</param>
/// <param name="permissions">权限集合</param>
/// <param name="claimType">声明类型</param>
/// <param name="issuer">发行人</param>
/// <param name="audience">订阅人</param>
/// <param name="signingCredentials">签名验证实体</param>
public PermissionRequirement(string deniedAction, List<Permission> permissions, string claimType, string issuer, string audience, SigningCredentials signingCredentials)
{
ClaimType = claimType;
DeniedAction = deniedAction;
Permissions = permissions;
Issuer = issuer;
Audience = audience;
SigningCredentials = signingCredentials;
}
}

在Startup中的 ConfigureServices 方法中注册服务。其中 rsa.ImportPkcs8PublicKey 这是一个扩展方法,来源于 RSAExtensions 包,大家看一下这位大哥的Github https://github.com/stulzq/RSAExtensions 。这个包提供了导入 pkcs8 格式公钥的方法,以及其他好用的方法。
public void ConfigureServices(IServiceCollection services)
{

#region 其他服务注册
...
#endregion



//添加JWT验证 对称密钥 --- 弃用
//services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
// .AddJwtBearer(option =>
// {
// option.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
// {
// ValidateIssuer = true,
// ValidateAudience = true,
// ValidateLifetime = true,
// ValidateIssuerSigningKey = true,
// ValidAudience = "zxservice",
// ValidIssuer = GlobalConfig.JwtIssuer,
// IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(GlobalConfig.JwtSecurityKey)),


// };
// });



//================================================================================================================
// 自定义权限校


//对称密钥
//var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(GlobalConfig.JwtSecurityKey)), SecurityAlgorithms.HmacSha256);

//非对称密钥
var rsa = RSA.Create();
byte[] publicKey = Convert.FromBase64String("公钥Base64(掐头去尾,不带Begin...以及回车空格等格式的)");

//rsa.ImportPkcs8PublicKey 这是一个扩展方法,来源于 RSAExtensions 包,大家可以关注一下这位大哥的Github https://github.com/stulzq/RSAExtensions 。这个包提供了导入 pkcs8 格式公钥的方法。
rsa.ImportPkcs8PublicKey(publicKey);

var sKey = new RsaSecurityKey(rsa);

//非对称密钥
var signingCredentials = new SigningCredentials(sKey, SecurityAlgorithms.RsaPKCS1);

var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingCredentials.Key,
ValidateIssuer = true,
ValidIssuer = GlobalConfig.JwtIssuer,
ValidateAudience = true,
ValidAudience = GlobalConfig.JwtAudience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};


services.AddAuthorization(option =>
{
//这个集合模拟用户权限表,可从数据库中查询出来
var permission = new List<Permission> {
new Permission { Url="/api/v1/Test", Name="supadmin", Method="get" },
new Permission { Url="/api/v1/MyBasicInfo", Name="supadmin", Method="get" },
};

var permissionRequirement = new PermissionRequirement("", permission, "Role", GlobalConfig.JwtIssuer, GlobalConfig.JwtAudience, signingCredentials);

//添加自定义验证规则
option.AddPolicy("ZxPermission", policy => policy.Requirements.Add(permissionRequirement));

}).AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidAudience = GlobalConfig.JwtAudience,
ValidIssuer = GlobalConfig.JwtIssuer,
IssuerSigningKey = signingCredentials.Key,
};
});

services.AddSingleton<IAuthorizationHandler, PermissionHandler>();

//================================================================================================================

}

 

测试

首先在 swagger 中调用api获取 token


然后用 PostMan 模拟请求并带上Token 注意 Type 要选择 Bearer Token


其实也可以访问 https://jwt.io/ 校验Token 。不过需要注意的是,在https://jwt.io/ 上输入公钥时 必须带上 -----BEGIN PUBLIC KEY----- 以及 -----END PUBLIC KEY----- 不然无法验签。


***注意*** 以上文章仅作为个人学习记录,文中的方案有很多还未详细处理。因为怕日后忘了,所以先记录下来。如有错误还请指正 谢谢!

标签:Core,自定义,get,身份验证,var,new,httpContext,true,public
From: https://www.cnblogs.com/Alex80/p/17588531.html

相关文章

  • .NET Core 验证码 - LazyCaptcha
    1.前言    在开发网站登录,安全验证等方面的时候,我们经常会使用到验证码,以前我们制作验证码的时候,一般都会写大量的代码完成验证码的制作。有没有现成,又好用的验证码工具插件呢?当然是有的,这里就介绍一个开源又好用的验证码工具LazyCaptcha。2.LazyCaptcha介绍   ......
  • C# 扫描并读取图片中的文字(.NET Core)
    本文介绍如何通过C#程序来扫描并读取图片中的文字,这里以创建一个.NetCore程序为例。下面是具体步骤,供参考。程序测试环境:VisualStudio版本要求不低于2017图片扫描工具:Spire.OCRfor.NET图片格式:png(这里的图片格式支持JPG、PNG、GIF、BMP、TIFF等格式)扫描的图片文字:中文(......
  • 【HMS Core】统一扫描连续扫码、闪光灯关闭问题
    ​ 【问题描述1】使用DefaultViewMode进行扫码,如何实现连续扫码 【解决方案】在默认扫码模式DefaultView中,功能是集成在SDK内部的,无法设置连续扫码模式等信息。可以使用CustomizedViewMode这种模式,它提供了相关的api可以设置是否连续扫码:通过setContinuouslyScan方法......
  • Unity 创建自定义包
    1.包的类型(1)嵌入式包(包文件夹在项目文件夹下,在其下的Packages子文件夹中)(2)本地包  (包文件夹在项目文件夹外,路径不固定)2.创建“本地包”的步骤官方文档: 创建自定义包文件示例1:package.json文件,如下图所示:{"name":"com.example.di......
  • vue导出自定义的html
    vue导出.html文件HTML页面由.css、htmlDom标签组成,将css设置成js通过export导出,htmlDom可以通过$el.innerHTML获得,也可通过document.getElementById('id')获得。然后构造html页面,使用createObjectURL创建一个文件流下载。代码如下:import{resumecss}from'@/styles/download.......
  • rdma-core之rdma_client.c
    库函数、全局变量#include<stdio.h>#include<stdlib.h>#include<string.h>#include<netdb.h>#include<errno.h>#include<getopt.h>#include<rdma/rdma_cma.h>#include<rdma/rdma_verbs.h>staticconstchar*server......
  • AndroidAnnotations 自定义控件 ant编译 找不到类
    问题:今天使用jenkins+ant编译android项目,一直编译不通过,报错内容就是,自定义控件找不到类。确认了几次svn提交,项目代码完全提交,所以想到是不是代码的问题。故回头查询代码。在代码中使用了下划线类。这个类是在AA中自动生成的,而报错的地方,正是编译生成的地方,觉得可能和编......
  • 如何使用地图软件制作一个自定义的旅游线路示意图 All In One
    如何使用地图软件制作一个自定义的旅游线路示意图AllInOneVlog视频Vlog视频博客Videoblog、Videolog如何用GoogleMap制作旅游路线图创建新地图添加线路(自动生成连线)预览分享https://www.google.com/maps/d/https://www.google.com/maps/d/ed......
  • NET7下EFCORE的通用增删查改类
    NET7下EFCORE的通用增删查改类代码摘录自《深入浅出ASP.NETCORE》 ///<summary>///所有仓储的约定,此接口仅作为约定,用于标识他们///</summary>///<typeparamname="TEntity">传入仓储的实体模型</typeparam>///<typeparamname="TPrimaryKey&quo......
  • 如何在Avalonia11中设置自定义字体
    如何在Avalonia11中设置自定义字体由于avalonia默认的中文字体显示的效果不太理想,我们需要下载一些自定义的字体,来优化UI的显示效果。avalonia的官方文档地址。对我在项目中运用的自定义字体操作做一个记录和分享,希望对大家有帮助。对应的代码都在基于Avalonia的GPT的AI会话项目......