介绍 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
services.AddTransient
…
它们被添加到服务集合中,现在作为控制器构造函数中的参数可用。 创建JWT索赔 函数登录控制器JwtIssuerController创建JWT索赔。这个过程非常简单: 找到用户。检查密码。创建发行者、主题、电子邮件唯一Id和问题索赔。从存储中收集用户角色(声明)根据配置参数和密钥创建JWT。向调用者发送令牌 隐藏,收缩,复制Code
namespace Security.Controllers
{
[AllowAnonymous]
[Route("api/security")]
public class JwtIssuerController : Controller
{
private readonly IJwtIssuerOptions JwtOptions;
private readonly UserManager
private readonly SignInManager
private readonly 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
var userManager = app.ApplicationServices.GetService
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
手机扫一扫
移动阅读更方便
你可能感兴趣的文章