dotnet core JWT Demo
JWT介绍
JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案。JWT的官网地址:https://jwt.io/.
通俗地来讲,JWT是能代表用户身份的令牌,可以使用JWT令牌在api接口中校验用户的身份以确认用户是否有访问api的权限。
JWT中包含了身份认证必须的参数以及用户自定义的参数,JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
组成结构
在紧凑的形式中,JSON Web Tokens由dot(.)分隔的三个部分组成,它们是:
- Header 头
- Payload 有效载荷
- Signature 签名
因此,JWT通常长这个样子: xxxxx.yyyyy.zzzzz
Header
标头通常由两部分组成: 令牌的类型, 即JWT, 以及正在使用的签名算法,例如HMAC, SHA256或RSA。
例如:
{
"typ": "JWT",
"alg": "HS256"
}
然后,这个JSON被编码为Base64Url,形成JWT的第一部分。
Payload
Payload部分也是一个JSON对象, 用来存放实际需要传递的数据. JWT规定了7个官方字段:
- iss (issuer): 签发人
- exp (expiration time): 过期时间
- sub (subject): 主题
- aud (audience): 受众
- nbf (Not Before): 生效时间
- iat (Issued At): 签发时间
- jti (JWT ID): 编号
除了官方字段, 你还可以在这个部分定义私有字段, 但是它默认是不加密的, 任何人都可以读到, 所以不要把秘密信息放在这个部分. 这个JSON对象也要使用Base64URL算法转成字符串.
Signature
Signature部分是对前两部分的签名, 防止数据篡改.
首先需要指定一个密钥(secret), 这个密钥只有服务器才知道, 不能泄露给用户. 然后使用Header里面指定的签名算法(默认是HMAC SHA256), 按照下面的公式产生签名:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
使用方法
项目源码请看我的Gitee.
项目基础
该项目需要需要使用以下两个nugget包:
- System.IdentityModel.Tokens.Jwt
- Microsoft.AspNetCore.Authentication.JwtBearer
从概念上主要分为两个部分: - Authentication: 公司给你发的门禁卡.
- Authorization: 财务保险柜的钥匙.
一开始一直对这两个概念模棱两可, 每次都是看了又丢了, 其实不难, 不深究了.
项目结构
下面以项目结构对代码进行一个粗略的解说:
- 注入服务
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(option =>
{
//添加JWT Scheme
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(option =>
{
//添加JWT验证
option.TokenValidationParameters = new TokenValidationParameters()
{
ValidateLifetime = true,//是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(30),
ValidateAudience = true,//是否验证Audience
//ValidAudience = Const.GetValidudience(),//Audience
//这里采用动态验证的方式,在重新登陆时,刷新token,旧token就强制失效了
AudienceValidator = (m, n, z) =>
{
return m != null && m.FirstOrDefault().Equals(Const.ValidAudience);
},
ValidateIssuer = true,//是否验证Issuer
ValidIssuer = Const.Domain,//Issuer,这两项和前面签发jwt的设置一致
ValidateIssuerSigningKey = true,//是否验证SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//拿到SecurityKey
};
option.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = context =>
{
//Token expired
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
context.Response.Redirect("/");
}
return Task.CompletedTask;
}
};
});
//添加策略健全模式
services.AddAuthorization(option =>
{
option.AddPolicy("Permission", policy => policy.Requirements.Add(new PolicyRequirement()));
});
//注入授权Handler
services.AddSingleton<IAuthorizationHandler, PolicyHandler>();
//注入HttpContext的祖先
services.AddHttpContextAccessor();
services.AddControllersWithViews();
}
- 启用服务
app.UseAuthentication();
- 权限要求
public class PolicyRequirement : IAuthorizationRequirement
{
/// <summary>
/// 用户权限集合
/// </summary>
public List<UserPermission> UserPermissions { get; private set; }
/// <summary>
/// 无权限action
/// </summary>
public string DeniedAction { get; set; }
/// <summary>
/// 构造
/// </summary>
public PolicyRequirement()
{
//没有权限则跳转到这个路由
DeniedAction = new PathString("/api/auth/nopermission");
//用户有权限访问的路由配置,当然可以从数据库获取
UserPermissions = new List<UserPermission> {
new UserPermission { Url="/api/values/authorization", UserName="admin"},
};
}
}
/// <summary>
/// 用户权限承载实体
/// </summary>
public class UserPermission
{
/// <summary>
/// 用户名
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 请求Url
/// </summary>
public string Url { get; set; }
}
- 权限处理
public class PolicyHandler : AuthorizationHandler<PolicyRequirement>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public PolicyHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement)
{
//赋值用户权限
var userPermissions = requirement.UserPermissions;
//从AuthorizationHandlerContext转成HttpContext,以便取出表求信息
var httpContext = _httpContextAccessor.HttpContext;
//请求Url
var questUrl = httpContext.Request.Path.Value.ToUpperInvariant();
//是否经过验证
var isAuthenticated = httpContext.User.Identity.IsAuthenticated;
if (isAuthenticated)
{
if (userPermissions.GroupBy(g => g.Url).Any(w => w.Key.ToUpperInvariant() == questUrl))
{
//用户名
var userName = httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.NameIdentifier).Value;
if (userPermissions.Any(w => w.UserName == userName && w.Url.ToUpperInvariant() == questUrl))
{
context.Succeed(requirement);
}
else
{
//无权限跳转到拒绝页面
httpContext.Response.Redirect(requirement.DeniedAction);
}
}
else
{
context.Succeed(requirement);
}
}
else
{
httpContext.Response.Redirect(requirement.DeniedAction);
}
return Task.CompletedTask;
}
}
- 授权
[HttpGet]
public IActionResult Get(string userName, string pwd)
{
if (CheckAccount(userName, pwd, out string role))
{
//每次登陆动态刷新
Const.ValidAudience = userName + pwd + DateTime.Now.ToString();
// push the user’s name into a claim, so we can identify the user later on.
//这里可以随意加入自定义的参数,key可以自己随便起
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddMinutes(30)).ToUnixTimeSeconds()}"),
new Claim(ClaimTypes.NameIdentifier, userName),
new Claim("Role", role)
};
//sign the token using a secret key.This secret will be shared between your API and anything that needs to check that the token is legit.
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
//.NET Core’s JwtSecurityToken class takes on the heavy lifting and actually creates the token.
var token = new JwtSecurityToken(
//颁发者
issuer: Const.Domain,
//接收者
audience: Const.ValidAudience,
//过期时间
expires: DateTime.Now.AddMinutes(30),
//签名证书
signingCredentials: creds,
//自定义参数
claims: claims
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
else
{
return BadRequest(new { message = "username or password is incorrect." });
}
}
- 访问授权
// Authentication验证门禁卡
[Authorize]
// Authorization验证保险柜钥匙
[Authorize("Permission")]
项目源码请看我的Gitee.
调试方法
Postman
这里用Postman调试的时候出现了一点小插曲, 因为.Net Core3.0会自己生成https证书, 不知道是Postman不认他还是为什么, 请求一直发不出去, 这里需要设置关闭ssl验证.
Chrome
这里可以直接在Chrome控制台里面写请求:
fetch('https://localhost:5001/api/values/authorization',{
headers: {
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOiIxNTcyOTQxNDM1IiwiZXhwIjoxNTcyOTQzMjM1LCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6ImFkbWluIiwiUm9sZSI6ImFkbWluIiwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImF1ZCI6ImFkbWluMTEvMDUvMjAxOSAxNjoxMDozNSJ9.-pQK03wUYH97SDRxaN51CkcpXcs9B6qNwnZ4dfRgv3s'
}
})
.then(res => res.json())
.then(console.log)
dotnet core JWT Demo的更多相关文章
- Dotnet core使用JWT认证授权最佳实践(一)
最近,团队的小伙伴们在做项目时,需要用到JWT认证.遂根据自己的经验,整理成了这篇文章,用来帮助理清JWT认证的原理和代码编写操作. 一.JWT JSON Web Token (JWT)是一个开放标准 ...
- Dotnet core使用JWT认证授权最佳实践(二)
最近,团队的小伙伴们在做项目时,需要用到JWT认证.遂根据自己的经验,整理成了这篇文章,用来帮助理清JWT认证的原理和代码编写操作. 第一部分:Dotnet core使用JWT认证授权最佳实践(一) ...
- spring cloud+dotnet core搭建微服务架构:Api授权认证(六)
前言 这篇文章拖太久了,因为最近实在太忙了,加上这篇文章也非常长,所以花了不少时间,给大家说句抱歉.好,进入正题.目前的项目基本都是前后端分离了,前端分Web,Ios,Android...,后端也基本 ...
- 一文说通Dotnet Core的中间件
前几天,公众号后台有朋友在问Core的中间件,所以专门抽时间整理了这样一篇文章. 一.前言 中间件(Middleware)最初是一个机械上的概念,说的是两个不同的运动结构中间的连接件.后来这个概念 ...
- dotNet Core开发环境搭建及简要说明
一.安装 .NET Core SDK 在 Windows 上使用 .NET Core 的最佳途径:使用Visual Studio. 免费下载地址: Visual Studio Community 20 ...
- 将app接口服务器改为dotnet core承载
昨天我的一个 app 的接口服务器挂掉了,国外的小鸡意外的翻车,连同程序和数据一起,猝不及防.我的服务端程序是 asp.net mvc ,小鸡是 256 M 的内存跑不了 windows 系统,装的 ...
- spring cloud+dotnet core搭建微服务架构:配置中心(四)
前言 我们项目中有很多需要配置的地方,最常见的就是各种服务URL地址,这些地址针对不同的运行环境还不一样,不管和打包还是部署都麻烦,需要非常的小心.一般配置都是存储到配置文件里面,不管多小的配置变动, ...
- spring cloud+dotnet core搭建微服务架构:配置中心续(五)
前言 上一章最后讲了,更新配置以后需要重启客户端才能生效,这在实际的场景中是不可取的.由于目前Steeltoe配置的重载只能由客户端发起,没有实现处理程序侦听服务器更改事件,所以还没办法实现彻底实现这 ...
- dotnet core开源博客系统XBlog介绍
XBlog是dotnet core平台下的个人博客开源系统,它只需要通过Copy的方式即可以部署到Linux和windows系统中:如果你有安全证书那只需要简单配置一下即可提供安全的Https服务.接 ...
随机推荐
- Error instantiating class cn.edu.zju.springmvc.pojo.Items with invalid types () or values (). 报错解决方法
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.Reflecti ...
- Java与设计模式之单例模式(下) 安全的单例模式
关于单例设计模式,<Java与设计模式之单例模式(上)六种实现方式>介绍了6种不同的单例模式,线程安全,本文介绍该如何保证单例模式最核心的作用——“实现该模式的类有且只有一个实 ...
- 关于aes加密
aes加密有几种模式:CBC,AES-128bit, Pkcs7补码方式(后台有可能是PKCS5Padding,是一样的),安卓和ios的key密钥对长度没有要求,但是前端web的密钥和偏移量必须是1 ...
- SQL学习笔记(二)
连接查询 数据准备 例1:查询学生信息及学生的成绩 等值连接 此方法会产生笛卡尔积,生成的记录总数=表1的总数*表2的总数,会产生临时表 内连接 select * from 表1 inner join ...
- 自定义Hooks函数获取窗口大小(十一)
其实自定义Hooks函数和用Hooks创建组件很相似,跟我们平时用JavaScript写函数几乎一模一样,可能就是多了些React Hooks的特性,自定义Hooks函数偏向于功能,而组件偏向于界面和 ...
- k8s 集群部署--学习
kubernetes是google开源的容器集群管理系统,提供应用部署.维护.扩展机制等功能,利用kubernetes能方便管理跨集群运行容器化的应用,简称:k8s(k与s之间有8个字母) Pod:若 ...
- Python脚本基础运算和算法
原文地址:https://www.cnblogs.com/ailiailan/p/10141741.html 通过关注“常见”脚本,是对代码的一个很好的学习和总结的方式. 1.冒泡排序 lis = [ ...
- android studio: 快捷键生成getter/setter方法时自动加m的问题
平时使用Android Studio 在写实体类的时候,习惯给实体类的成员变量前面加上一个"m" 修饰符表示这是一个成员变量,这也是搞java的一种约定俗成的写法,本来这是没有问题 ...
- springboot启动提示连接mysql报错:java.sql.SQLNonTransientConnectionException: CLIENT_PLUGIN_AUTH is required
如题,启动springboot报错: -- :: --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized wi ...
- 【插件】thinkphp5+百度编辑器自定义上传
1 官方下载sdk 2 在引入编辑器页面.写入js // 百度编辑器 UE.Editor.prototype._bkGetActionUrl = UE.Editor.prototype.getActi ...