JWT安全性第1部分,创建令牌
阅读原文时间:2023年07月12日阅读:2

介绍 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(Configuration.GetSection(nameof(JwtIssuerSettings)));
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 UserManager;
private readonly SignInManager SignInManager;
private readonly RoleManager 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