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的更多相关文章

  1. Dotnet core使用JWT认证授权最佳实践(一)

    最近,团队的小伙伴们在做项目时,需要用到JWT认证.遂根据自己的经验,整理成了这篇文章,用来帮助理清JWT认证的原理和代码编写操作. 一.JWT JSON Web Token (JWT)是一个开放标准 ...

  2. Dotnet core使用JWT认证授权最佳实践(二)

    最近,团队的小伙伴们在做项目时,需要用到JWT认证.遂根据自己的经验,整理成了这篇文章,用来帮助理清JWT认证的原理和代码编写操作. 第一部分:Dotnet core使用JWT认证授权最佳实践(一) ...

  3. spring cloud+dotnet core搭建微服务架构:Api授权认证(六)

    前言 这篇文章拖太久了,因为最近实在太忙了,加上这篇文章也非常长,所以花了不少时间,给大家说句抱歉.好,进入正题.目前的项目基本都是前后端分离了,前端分Web,Ios,Android...,后端也基本 ...

  4. 一文说通Dotnet Core的中间件

    前几天,公众号后台有朋友在问Core的中间件,所以专门抽时间整理了这样一篇文章.   一.前言 中间件(Middleware)最初是一个机械上的概念,说的是两个不同的运动结构中间的连接件.后来这个概念 ...

  5. dotNet Core开发环境搭建及简要说明

    一.安装 .NET Core SDK 在 Windows 上使用 .NET Core 的最佳途径:使用Visual Studio. 免费下载地址: Visual Studio Community 20 ...

  6. 将app接口服务器改为dotnet core承载

    昨天我的一个 app 的接口服务器挂掉了,国外的小鸡意外的翻车,连同程序和数据一起,猝不及防.我的服务端程序是 asp.net mvc ,小鸡是 256 M 的内存跑不了 windows 系统,装的 ...

  7. spring cloud+dotnet core搭建微服务架构:配置中心(四)

    前言 我们项目中有很多需要配置的地方,最常见的就是各种服务URL地址,这些地址针对不同的运行环境还不一样,不管和打包还是部署都麻烦,需要非常的小心.一般配置都是存储到配置文件里面,不管多小的配置变动, ...

  8. spring cloud+dotnet core搭建微服务架构:配置中心续(五)

    前言 上一章最后讲了,更新配置以后需要重启客户端才能生效,这在实际的场景中是不可取的.由于目前Steeltoe配置的重载只能由客户端发起,没有实现处理程序侦听服务器更改事件,所以还没办法实现彻底实现这 ...

  9. dotnet core开源博客系统XBlog介绍

    XBlog是dotnet core平台下的个人博客开源系统,它只需要通过Copy的方式即可以部署到Linux和windows系统中:如果你有安全证书那只需要简单配置一下即可提供安全的Https服务.接 ...

随机推荐

  1. Java SpringBoot使用Redis缓存和Ehcache

    <?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http:// ...

  2. Zabbix优化

    参考 zabbix默认的配置即使机器128核心,256内存,只能抗住10-20台的监控,如果再多就需要修改配置了. 一.配置文件 server端配置文件添加如下 StartPollers=160 St ...

  3. [RK3399] ES8316+NS4150 播放视频只有背景音,播放歌曲有的有声音,有的无声音

    CPU:RK3399 系统:Android 音频IC:ES8316 功放IC:NS4150 以前也在 RK3399 上调试过 ES8316,功能都正常,新主板只是更换了功放IC,就出现无声音的问题(仔 ...

  4. 和重复搭建开发环境说 Bye Bye 之Vagrant

    每每新同事入职,都要在自己电脑上配置一堆环境,费神费力:每每开发测试都要重新配置开发环境,手工搭建,步骤很繁琐,极易出错. 大神在时,大神搭建,大神不在,以手抚膺坐长叹.为此,VVVVVagrant横 ...

  5. ubuntu上安装jdk

    使用安装包安装:JDK官网下载地址: https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.h ...

  6. Chrome与chromedriver.exe的版本对应

    Chrome与chromedriver.exe的版本对应 分类专栏: pyhton3.7+selenium3   转:https://blog.csdn.net/weixin_44545954/art ...

  7. Python3基础 complex real imag __abs__ 取复数的实部 虚部 模

             Python : 3.7.3          OS : Ubuntu 18.04.2 LTS         IDE : pycharm-community-2019.1.3    ...

  8. flutter的 图片组件基本使用

    import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends Statele ...

  9. System.InvalidOperationException:“No coercion operator is defined between types 'System.Int16' and 'System.Boolean'.”

    modelBuilder.Entity<MentItems>().Property(e=>e.IsValid) .HasColumnType("bit(1)") ...

  10. C++类成员存储大小

    1.对象分布图 2.解析 每个类的大小只有其成员变量大小,其中包括:类成员属性,虚函数指针: 而其他没有如:静态变量[静态区],普通函数.静态函数[代码区] 3.总结 类对象的sizeof只包含成员变 ...