JWT安全性第1部分,创建令牌
介绍 JWT (JSON Web Token)作为保护Web站点和REST服务的标准越来越流行。我将讨论如何使用。net Core为REST服务和MVC web应用程序实现JWT安全性。我把智威汤逊的安全分成了3个博客 使用JWT安全web应用程序创建JWT安全REST服务 这是三个博客中的第一个,我从JWT的一个小解释开始。 JWT底漆 JWT (JSON Web令牌)是一个开放的安全协议,用于在双方之间安全地交换声明。服务器生成或发出令牌,并通过密钥签名。客户端还知道密钥和密钥,并可以验证令牌是否真实。令牌包含身份验证和授权声明。身份验证就是验证某人是否真的是他声称的那个人。授权是指授予用户访问资源或执行特定任务的权限。例如,用户A可以查看支付,而用户B可以执行支付。JWT是独立的。因为JWT is 一个协议,而不是一个框架,它可以跨不同的语言工作,比如。net, Java Python等等。JWT is 通常通过将JWT添加到请求的头来传输,但也可以作为URL中的参数使用。这个传输使JWT无状态。 JWT结构 JWT分为三个部分: 头载荷的签名 这些部分用一个点隔开。 aaaa.bbbb.cccc 头 标题和有效负载有一个或多个键值对。头包含标记类型('typ')和散列算法('alg') SHA256。 隐藏,复制Code
{
"alg":"HS256",
"typ":"JWT"
}
头部和有效负载部分是base64编码,这使得头部部分: 隐藏,复制Code
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
有效载荷 有效负载部分是最有趣的部分,因为它包含所有声明。有三种登记索赔类型,公共索赔和私人索赔。 注册要求 注册的索赔是智威汤逊标准的一部分,在所有的实施中都有相同的目的。为了保持智威汤逊的小尺寸,密钥总是3个字符长。以下是一些简短的清单: iss的发行者识别出谁签发了JWT。子主题标识JWT的主体(读取用户)。aud受众识别JWT的目标受众。exp过期设置了过期日期,当过期时,JWT必须被拒绝。nbf不是之前。设定日期前,JWT不得使用。iat发行。设置创建JWT的日期。jti的JWT唯一标识符。用于一次性令牌,并防止令牌重放。 所有登记的索赔日期都采用Unix纪元日期格式,描述UTC时间1970年1月1日之后的几秒。 公开宣称 公开声明包含更多的一般资料,例如“姓名”。公众姓名也被登记,以防止与其他索赔冲突。 私人索赔 私人索赔是由发行方和受众达成协议的。经常检查私人申索是否与现有的申索冲突。声明“角色”是我们稍后将使用的私有声明示例。 负载的例子 隐藏,复制Code
{
"iss": "JwtServer",
"sub": "hrmanager",
"email": "hrmanager@xyz.com",
"jti": "e971bd9c-7655-41d5-9c49-fabc054dc466",
"iat": 1503922683,
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role": [
"Employee",
"HR-Worker",
"HR-Manager"
],
"Department": [
"HR",
"HR"
],
"nbf": 1503922683,
"exp": 1503924483
}
将导致 隐藏,复制Code
eyJpc3MiOiJKd3RTZXJ2ZXIiLCJzdWIiOiJocm1hbmFnZXIiLCJlbWFpbCI6ImhybWFuYWdlckB4eXouY29tIiwianRpIjoiZTk3MWJkOWMtNzY1NS00MWQ1LTljNDktZmFiYzA1NGRjNDY2IiwiaWF0IjoxNTAzOTIyNjgzLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsiRW1wbG95ZWUiLCJIUi1Xb3JrZXIiLCJIUi1NYW5hZ2VyIl0sIkRlcGFydG1lbnQiOlsiSFIiLCJIUiJdLCJuYmYiOjE1MDM5MjI2ODMsImV4cCI6MTUwMzkyNDQ4M30
签名 到目前为止,JWT还没有什么安全可言。所有数据都是使用base64编码的,虽然不是人类可读的,但是很容易将其解码成可读的文本。这就是签名出现的地方。有了签名,我们可以验证JWT是否是真实的,没有被篡改。签名是从头、有效负载和密钥计算出来的。 隐藏,复制Code
var headerAndPayload = base64Encode(header) + "." + base64Encode(payload); var secretkey = "@everone:KeepitSecret!"; signature = HMACSHA256(headerAndPayload, secretkey);
密钥是对称的,并且发布者和客户端都知道。不用说,在存储密钥的地方一定要小心! 把它们放在一起 下面的屏幕转储是在https://jwt的帮助下构造的。可以测试和调试JWT声明的io/。左边的窗格保存JWT,另一个窗格显示提取的头部和有效负载。如果您添加了密钥,页面也会验证签名。 JWT安全概述 图1解决方案概况 解决方案概述展示了三个独立的服务器:Web应用程序、RESTful服务和JWT发布服务器。它们可以托管在一台服务器和一个项目中,但我为它做了三个项目。通过这种方式,每个服务器的配置方式更加清晰。因为JWT是自我包含的,所以JWT发布者和REST服务之间不需要某种连接来验证JWT索赔。 一般JWT流 JWT的基本流程很简单: 用户在web应用程序上输入登录凭据。web应用程序将登录凭据发送给JWT发布者并请求JWT索赔。JWT发布者使用用户数据库验证登录凭据。JWT发行者根据用户数据库中的索赔和角色创建JWT,并为有限的生命周期(30分钟)添加“exp”(Expires)索赔。JWT发行者将JWT发送到web应用程序。Web应用程序接收JWT并将其存储在一个身份验证cookie中。Web应用程序验证JWT和解析身份验证的有效负载授权。Web应用程序将JWT添加到REST服务调用。 优点和缺点 优点: 相对简单。无论你选择什么,安全从来都不是件容易的事。JWT是一种聪明的设计,与做“艰苦”工作的。net库相结合,使JWT相对容易实现。REST服务是真正无状态的,正如它应该是的那样。在大多数情况下,安全性为身份验证添加了某种类型的会话管理。无状态使可伸缩。如果您需要更多的服务器来处理工作负载,则不需要在所有服务器之间共享会话。这使得扩展更容易,出错的可能性更小。可跨不同服务使用。JWT是自包含的,服务可以在不访问用户数据库的情况下进行授权。JWT为临时授权提升提供了很好的选项。声明可以在用户会话期间添加或删除。例如,您可以向成功通过执行支付的双向身份验证的用户添加声明。当付款成功执行时,可以删除索赔。通过这种方式,不需要创建跟踪用户状态的特殊方法。 缺点: JWT没有针对滑动到期的内置特性,尽管您可以自己构建它。秘密密钥非常重要。如果密钥以某种方式被盗或泄漏,安全性就会严重受损。 创建JWT发行者项目 主要任务是根据用户凭证交付JWT声明。项目is 使用单个用户帐户进行身份验证的标准MVC应用程序。 个人用户帐户认证用于确保网站的安全,方便访问用户及其角色和声明。我添加了软件包Microsoft.AspNetCore.Authentication。实际创建JWT的jwtholder。Because JWT不用于保护这个网站调用者,没有必要在启动期间注册jwt承载服务。在启动期间只配置JWT参数。 隐藏,复制Code
{
...
"JwtIssuerSettings": {
"Issuer": "JwtServer",
"ValidFor": 30, // minutes
"SecretKey": "@everone:KeepitSecret!"
},
...
对配置应用DI(依赖项注入)模式。类JwtIssuerSettings映射到appsettings中的配置节JwtIssuerSettings。json和类JwtIssuerFactory创建了IJwtIssuerOptions接口的实例。 隐藏,复制Code
public void ConfigureServices(IServiceCollection services)
{
...
// setup JWT parameters
services.Configure<JwtIssuerSettings>(Configuration.GetSection(nameof(JwtIssuerSettings)));
services.AddTransient<IJwtIssuerOptions, JwtIssuerFactory>();
...
它们被添加到服务集合中,现在作为控制器构造函数中的参数可用。 创建JWT索赔 函数登录控制器JwtIssuerController创建JWT索赔。这个过程非常简单: 找到用户。检查密码。创建发行者、主题、电子邮件唯一Id和问题索赔。从存储中收集用户角色(声明)根据配置参数和密钥创建JWT。向调用者发送令牌 隐藏,收缩,复制Code
namespace Security.Controllers
{
[AllowAnonymous]
[Route("api/security")]
public class JwtIssuerController : Controller
{
private readonly IJwtIssuerOptions JwtOptions;
private readonly UserManager<ApplicationUser> UserManager;
private readonly SignInManager<ApplicationUser> SignInManager;
private readonly RoleManager<IdentityRole> RoleManager; public JwtIssuerController(IJwtIssuerOptions jwtOptions,
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
RoleManager<IdentityRole> roleManager)
{
JwtOptions = jwtOptions;
UserManager = userManager;
SignInManager = signInManager;
RoleManager = roleManager;
} [HttpPost(nameof(Login))]
public async Task<IActionResult> Login([FromBody] LoginResource resource)
{
if (resource == null)
return BadRequest("Login resource must be asssigned"); var user = await UserManager.FindByEmailAsync(resource.Email); if (user == null || (!(await SignInManager.PasswordSignInAsync(user, resource.Password, false, false)).Succeeded))
return BadRequest("Invalid credentials"); String result = await CreateJwtTokenAsync(user); // Token is created, we can sign out
await SignInManager.SignOutAsync(); return Ok(result);
} /// <summary>
/// Fetch user roles and claims from storage
/// </summary>
/// <param name="user">application user</param>
/// <returns>JWT token</returns>
private async Task<String> CreateJwtTokenAsync(ApplicationUser user)
{
// Create JWT claims
var claims = new List<Claim>(new[]
{
// Issuer
new Claim(JwtRegisteredClaimNames.Iss, JwtOptions.Issuer), // UserName
new Claim(JwtRegisteredClaimNames.Sub, user.UserName), // Email is unique
new Claim(JwtRegisteredClaimNames.Email, user.Email), // Unique Id for all Jwt tokes
new Claim(JwtRegisteredClaimNames.Jti, await JwtOptions.JtiGenerator()), // Issued at
new Claim(JwtRegisteredClaimNames.Iat, JwtOptions.IssuedAt.ToUnixEpochDate().ToString(), ClaimValueTypes.Integer64)
}); // Add userclaims from storage
claims.AddRange(await UserManager.GetClaimsAsync(user)); // Add user role, they are converted to claims
var roleNames = await UserManager.GetRolesAsync(user);
foreach (var roleName in roleNames)
{
// Find IdentityRole by name
var role = await RoleManager.FindByNameAsync(roleName);
if (role != null)
{
// Convert Identity to claim and add
var roleClaim = new Claim(ClaimTypes.Role, role.Name, ClaimValueTypes.String, JwtOptions.Issuer);
claims.Add(roleClaim); // Add claims belonging to the role
var roleClaims = await RoleManager.GetClaimsAsync(role);
claims.AddRange(roleClaims);
}
} // Prepare Jwt Token
var jwt = new JwtSecurityToken(
issuer: JwtOptions.Issuer,
audience: JwtOptions.Audience,
claims: claims,
notBefore: JwtOptions.NotBefore,
expires: JwtOptions.Expires,
signingCredentials: JwtOptions.SigningCredentials); // Serialize token
var result = new JwtSecurityTokenHandler().WriteToken(jwt); return result;
}
测试数据 在启动过程中,会创建一个内存中的数据库。它包含三个用户和三个角色,模拟了一个人力资源部门。 角色: 可以是任何公司成员。人力资源部的每个成员。人力资源经理,当然是人力资源老板。 用户: employee@xyz.com hrworker@xyz.com hrmanager@xyz.com|0>>>> 名称空间Microsoft.AspNetCore。标识包含RoleManager< IdentityRole>无需显式配置即可使用。在示例或文档中,您不会读到太多关于它的内容。因为该类对于管理系统中的角色非常有用,所以我们错失了一些机会。 隐藏,复制Code
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
// Fill empty inmemory database during development
if (env.IsDevelopment())
app.InitDb();
...
, 隐藏,收缩,复制Code
public static class InitDbExtensions
{
public static IApplicationBuilder InitDb(this IApplicationBuilder app)
{
var roleManager = app.ApplicationServices.GetService<RoleManager<IdentityRole>>();
var userManager = app.ApplicationServices.GetService<UserManager<ApplicationUser>>(); if (userManager.Users.Count() == 0)
{
Task.Run(() => InitRoles(roleManager)).Wait();
Task.Run(() => InitUsers(userManager)).Wait();
} return app;
} private static async Task InitRoles(RoleManager<IdentityRole> roleManager)
{
var role = new IdentityRole("Employee");
await roleManager.CreateAsync(role); role = new IdentityRole("HR-Worker");
await roleManager.CreateAsync(role);
await roleManager.AddClaimAsync(role, new Claim("Department", "HR")); role = new IdentityRole("HR-Manager");
await roleManager.CreateAsync(role);
await roleManager.AddClaimAsync(role, new Claim("Department", "HR"));
} private static async Task InitUsers(UserManager<ApplicationUser> userManager)
{
var user = new ApplicationUser() { UserName = "employee", Email = "employee@xyz.com" };
await userManager.CreateAsync(user, "password");
await userManager.AddToRoleAsync(user, "Employee"); user = new ApplicationUser() { UserName = "hrworker", Email = "hrworker@xyz.com" };
await userManager.CreateAsync(user, "password");
await userManager.AddToRoleAsync(user, "Employee");
await userManager.AddToRoleAsync(user, "HR-Worker"); user = new ApplicationUser() { UserName = "hrmanager", Email = "hrmanager@xyz.com" };
await userManager.CreateAsync(user, "password");
await userManager.AddToRoleAsync(user, "Employee");
await userManager.AddToRoleAsync(user, "HR-Worker");
await userManager.AddToRoleAsync(user, "HR-Manager");
}
}
}
测试JWT索赔 我通过添加包装Swashbuckle来增加Swagger。AspNetCore进行测试。你可以在这里阅读。更多如何配置swagger。简而言之,事情是这样的 隐藏,复制Code
public void ConfigureServices(IServiceCollection services)
{
...
// Register the Swagger generator, defining one or more Swagger documents
services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("Bearer", new ApiKeyScheme()
{
Description = "Authorization format : Bearer {token}",
Name = "Authorization",
In = "header",
Type = "apiKey"
}); c.SwaggerDoc("v1", new Info { Title = "Security Api", Version = "v1" });
});
...
隐藏,复制Code
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
// Enable middleware to serve generated Swagger as a JSON endpoint.
app.UseSwagger(); // Enable middleware to serve swagger-ui (HTML, JS, CSS etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Security Api v1");
});
...
Swagger现在可以通过http://localhost:49842/swagger/进行测试 我们可以在https://jwt.io/测试响应 一切看起来都很好,我们可以开始保护REST服务了。 Visual Studio启动项目 有时,Visual Studio启动项目会丢失,从而阻止应用程序运行。右键点击解决方案,选择“设置启动项目……” 修复启动设置: , 结论 这个博客演示了如何设置JWT (JSON Web令牌)发行方。无状态、自包含、可扩展和其他特性使JWT成为一种聪明的设计。在包的帮助下,JWT很好地集成了。net Core,而且安装起来不费什么力气。 下一篇文章:JWT安全第2部分,安全REST服务 进一步的阅读 JWT JSON Web令牌 JWT标准 JWT索赔 Unix纪元Datetime 设置大摇大摆 , 版本 1.0 2017-08-31首次发布 1.1 2017-09-05源代码升级为Dot Net Core 2.0 , 本文转载于:http://www.diyabc.com/frontweb/news19620.html
JWT安全性第1部分,创建令牌的更多相关文章
- 安全性与收尾工作 创建基本的安全策略 精通ASP-NET-MVC-5-弗瑞曼
- JSON Web令牌(JWT)介绍与使用
手机端接口开发会遇到一个问题是,接口登录后需要返回一个Token.token首先有一点必须唯一,每次请求都需要把token给带上.基于必须唯一的特性,很多朋友在开发是都选择了uuid.是不是token ...
- 使用JWT的ASP.NET CORE令牌身份验证和授权(无Cookie)——第1部分
原文:使用JWT的ASP.NET CORE令牌身份验证和授权(无Cookie)--第1部分 原文链接:https://www.codeproject.com/Articles/5160941/ASP- ...
- 什么是JWT令牌认证?
当下,JWT(JSON Web Token)令牌认证已经变得越来越流行.本文主要介绍JWT令牌认证与传统的Session会话认证机制的区别. 为什么需要认证? HTTP是一种无状态协议,那就意味着当前 ...
- OAuth2.0实战!使用JWT令牌认证!
大家好,我是不才陈某~ 这是<Spring Security 进阶>的第3篇文章,往期文章如下: 实战!Spring Boot Security+JWT前后端分离架构登录认证! 妹子始终没 ...
- 深入浅出JWT(JSON Web Token )
1. JWT 介绍 JSON Web Token(JWT)是一个开放式标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间以JSON对象安全传输信息. 这些信息可以通过数字签名进行 ...
- Java JWT: JSON Web Token
Java JWT: JSON Web Token for Java and Android JJWT aims to be the easiest to use and understand libr ...
- 认识JWT
1. JSON Web Token是什么 JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的.自包含的方式,用于作为JSON对象在各方之间安全地传输信息.该 ...
- [转]认识JWT
本文转自:https://www.cnblogs.com/cjsblog/p/9277677.html 1. JSON Web Token是什么 JSON Web Token (JWT)是一个开放标准 ...
随机推荐
- Git在windows上的设置详解
这几天在学习使用Git版本管理工具,发现期间的各种配置还是挺繁琐的,而且好多命令的确记不住,于是写个blog记录下来,方便以后查阅. 1. 首先到GitHub官网上下载最新的Git,然后装上,装的过程 ...
- ctf常见源码泄露
前言 在ctf中发现很多源码泄露的题,总结一下,对于网站的搭建要注意删除备份文件,和一些工具的使用如git,svn等等的规范使用,避免备份文件出现在公网 SVN源码泄露 原理 SVN(subversi ...
- Linux—账号管理及命令使用详解
关注微信公众号:CodingTechWork,一起学习进步. 引言 在Linux系统中,我们常常会看到目录或文件的所属关系: [root@linux01 ~]# ll -d test.sh -rw ...
- js+canvas画随机4位验证码
啥都不说了,复制代码吧!!! <!DOCTYPE html> <html lang="en"> <head> <meta charset= ...
- JVM学习第三天(JVM的执行子系统)之类加载机制补充
昨晚没看完,今天继续 系统的类加载器 对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间.这句话可以表达得更通俗一些: ...
- JS数组去重的实现
其实数组去重的实现就分为两大类 利用语法自身键不可重复性 利用循环(递归)和数组方法使用不同的api来处理. 注意️:下列封装成方法的要在函数开始增加类型检测,为了让去重的实现代码更加简单易懂,封装时 ...
- PHP的七个数组指针函数
1. PHP的七个数组指针函数 函数 描述 reset() 将一个数组的内部指针重置到首位,并返回第一个元素的值 end() 将一个数组的内部指针移动到数组的最后一个元素所在的位置,并返回最后一个元素 ...
- spring boot+spring security集成以及Druid数据库连接池的问题
贴工程目录,其中bll目录下是service+dao层,common是一些公用的模块及功能类,web是controller层 用到了druid及Redis,工具及配置类目录(本文不介绍如何配置drui ...
- Java中AQS基本实现原理
一.AQS概述 AQS全名AbstractQueuedSynchronizer,意为抽象队列同步器,JUC(java.util.concurrent包)下面的Lock和其他一些并发工具类都是基于它来实 ...
- d3力导图绘制节点间多条关系平行线的方法
之前用d3做了多条线之间的绘图是曲线表示的,现在产品要求改成平行线的样式,经过在网上的调研和自己的尝试,实践出一个可用的方法,分享给大家,先展示下结果: 事先声明,本方法是在以下参考网站上进行的结合和 ...