视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
asp.net core集成JWT的步骤记录
2020-11-27 14:43:04 责编:小采
文档


【什么是JWT】

JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案。

JWT的官网地址:https://jwt.io/

通俗地来讲,JWT是能代表用户身份的令牌,可以使用JWT令牌在api接口中校验用户的身份以确认用户是否有访问api的权限。

JWT中包含了身份认证必须的参数以及用户自定义的参数,JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

【什么时候应该使用JSON Web令牌?】

授权:这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由,服务和资源。Single Sign On是一种现在广泛使用JWT的功能,因为它的开销很小,并且能够在不同的域中轻松使用。

信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。因为JWT可以签名 - 例如,使用公钥/私钥对 - 您可以确定发件人是他们所说的人。此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。

【JWT有什么优势?】  

  1. 用户向服务器发送用户名和密码。
  2. 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。
  3. 服务器向用户返回一个 session_id,写入用户的 Cookie。
  4. 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。
  5. 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

  这种模式的问题在于,扩展性(scaling)不好。单机当然没有问题,如果是服务器集群,或者是跨域的服务导向架构,就要求 session 数据共享,每台服务器都能够读取 session。如果session存储的节点挂了,那么整个服务都会瘫痪,体验相当不好,风险也很高。

  相比之下,JWT的实现方式是将用户信息存储在客户端,服务端不进行保存。每次请求都把令牌带上以校验用户登录状态,这样服务就变成了无状态的,服务器集群也很好扩展。

【JWT令牌结构】

在紧凑的形式中,JSON Web Tokens由dot(.)分隔的三个部分组成,它们是:

  • Header 头
  • Payload 有效载荷
  • Signature 签名
  • 因此,JWT通常如下所示:

      xxxxx.yyyyy.zzzzz

    1.Header 头

    标头通常由两部分组成:令牌的类型,即JWT,以及正在使用的签名算法,例如HMAC SHA256或RSA。

    例如:

    {
     "alg": "HS256",
     "typ": "JWT"
    }

    然后,这个JSON被编码为BaseUrl,形成JWT的第一部分。

    2.Payload有效载荷

    Payload 部分也是一个 JSON 对象,用来存放实际需要传递的数据。JWT 规定了7个官方字段,供选用。

  • iss (issuer):签发人
  • exp (expiration time):过期时间
  • sub (subject):主题
  • aud (audience):受众
  • nbf (Not Before):生效时间
  • iat (Issued At):签发时间
  • jti (JWT ID):编号
  • 除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。例如:

    {
     "sub": "12345670",
     "name": "John Doe",
     "admin": true
    }

    注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。这个 JSON 对象也要使用 BaseURL 算法转成字符串。

    3.Signature 签名

    Signature 部分是对前两部分的签名,防止数据篡改。

    首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

    HMACSHA256(
     baseUrlEncode(header) + "." +
     baseUrlEncode(payload),
     secret)

    签名用于验证消息在此过程中未被更改,并且,在使用私钥签名的令牌的情况下,它还可以验证JWT的发件人是否是它所声称的人。  

    把他们三个全部放在一起

    输出是三个由点分隔的Base-URL字符串,可以在HTML和HTTP环境中轻松传递,而与基于XML的标准(如SAML)相比更加紧凑。

    下面显示了一个JWT,它具有先前的头和​​有效负载编码,并使用机密签名。

    如果您想使用JWT并将这些概念付诸实践,您可以使用jwt.io Debugger来解码,验证和生成JWT。

    【JSON Web令牌如何工作?】

    在身份验证中,当用户使用其凭据成功登录时,将返回JSON Web令牌。由于令牌是凭证,因此必须非常小心以防止出现安全问题。一般情况下,您不应该将令牌保留的时间超过要求。

    每当用户想要访问受保护的路由或资源时,用户代理应该使用承载模式发送JWT,通常在Authorization标头中。标题的内容应如下所示:

    Authorization: Bearer <token>

    在某些情况下,这可以是无状态授权机制。服务器的受保护路由将检查Authorization标头中的有效JWT,如果存在,则允许用户访问受保护资源。如果JWT包含必要的数据,则可以减少查询数据库以进行某些操作的需要,尽管可能并非总是如此。

    如果在标Authorization头中发送令牌,则跨域资源共享(CORS)将不会成为问题,因为它不使用cookie。

    下图显示了如何获取JWT并用于访问API或资源:

    应用程序向授权服务器请求授权校验用户身份,校验成功,返回token应用程序使用访问令牌访问受保护的资源【ASP.Net Core 集成JWT】

    前面我们介绍了JWT的原理,下面我们在asp.net core实际项目中集成JWT。

    首先我们新建一个Demo asp.net core 空web项目

    添加数据访问模拟api,ValuesController

    其中api/value1是可以直接访问的,api/value2添加了权限校验特性标签 [Authorize]

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    
    namespace Demo.Jwt.Controllers
    {
     [ApiController]
     public class ValuesController : ControllerBase
     {
     [HttpGet]
     [Route("api/value1")]
     public ActionResult<IEnumerable<string>> Get()
     {
     return new string[] { "value1", "value1" };
     }
    
     [HttpGet]
     [Route("api/value2")]
     [Authorize]
     public ActionResult<IEnumerable<string>> Get2()
     {
     return new string[] { "value2", "value2" };
     }
     }
    }

    添加模拟登陆,生成Token的api,AuthController

    这里模拟一下登陆校验,只验证了用户密码不为空即通过校验,真实环境完善校验用户和密码的逻辑。

    using System;
    using System.Collections.Generic;
    using System.IdentityModel.Tokens.Jwt;
    using System.Linq;
    using System.Security.Claims;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.IdentityModel.Tokens;
    
    namespace Demo.Jwt.Controllers
    {
     [Route("api/[controller]")]
     [ApiController]
     public class AuthController : ControllerBase
     {
     [AllowAnonymous]
     [HttpGet]
     public IActionResult Get(string userName, string pwd)
     {
     if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(pwd))
     {
     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.Name, userName)
     };
     var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey));
     var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
     var token = new JwtSecurityToken(
     issuer: Const.Domain,
     audience: Const.Domain,
     claims: claims,
     expires: DateTime.Now.AddMinutes(30),
     signingCredentials: creds);
    
     return Ok(new
     {
     token = new JwtSecurityTokenHandler().WriteToken(token)
     });
     }
     else
     {
     return BadRequest(new { message = "username or password is incorrect." });
     }
     }
     }
    }

    Startup添加JWT验证的相关配置

    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.IdentityModel.Tokens;
    using System;
    using System.Text;
    
    
    namespace Demo.Jwt
    {
     public class Startup
     {
     public Startup(IConfiguration configuration)
     {
     Configuration = configuration;
     }
    
     public IConfiguration Configuration { get; }
    
     // This method gets called by the runtime. Use this method to add services to the container.
     public void ConfigureServices(IServiceCollection services)
     {
     //添加jwt验证:
     services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
     .AddJwtBearer(options => {
     options.TokenValidationParameters = new TokenValidationParameters
     {
     ValidateIssuer = true,//是否验证Issuer
     ValidateAudience = true,//是否验证Audience
     ValidateLifetime = true,//是否验证失效时间
     ClockSkew = TimeSpan.FromSeconds(30),
     ValidateIssuerSigningKey = true,//是否验证SecurityKey
     ValidAudience = Const.Domain,//Audience
     ValidIssuer = Const.Domain,//Issuer,这两项和前面签发jwt的设置一致
     IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//拿到SecurityKey
     };
     });
    
     services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
     }
    
     // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
     public void Configure(IApplicationBuilder app, IHostingEnvironment env)
     {
     ///添加jwt验证
     app.UseAuthentication();
    
     if (env.IsDevelopment())
     {
     app.UseDeveloperExceptionPage();
     }
    
     app.UseMvc(routes =>
     {
     routes.MapRoute(
     name: "default",
     template: "{controller=Home}/{action=Index}/{id?}");
     });
     }
     }
    }

    最后把代码里面用到的一些相关常量也粘贴过来,Const.cs

    namespace Demo.Jwt
    {
     public class Const
     {
     /// <summary>
     /// 这里为了演示,写死一个密钥。实际生产环境可以从配置文件读取,这个是用网上工具随便生成的一个密钥
     /// </summary>
     public const string SecurityKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDI2a2EJ7m872v0afyoSDJT2o1+SitIeJSWtLJU8/Wz2m7gStexajkeD+Lka6DSTy8gt9UwfgVQo6uKjVLG5Ex7PiGOODVqAEghBuS7JzIYU5RvI543nNDAPfnJsas96mSA7L/mD7RTE2drj6hf3oZjJpMPZUQI/B1Qjb5H3K3PNwIDAQAB";
     public const string Domain = "http://localhost:5000";
     }
    }

    到这里,已经是我们项目的所有代码了。

    如果需要完整的项目代码,Github地址:https://github.com/sevenTiny/Demo.Jwt

    【JWT测试】

    我们找一个趁手的工具,比如fiddler,然后把我们的web站点运行起来

    首先调用无权限的接口:http://localhost:5000/api/value1

    正确地返回了数据,那么接下来我们测试JWT的流程

    1. 无权限

    首先我们什么都不加调用接口:http://localhost:5000/api/value2

    返回了状态码401,也就是未经授权:访问由于凭据无效被拒绝。 说明JWT校验生效了,我们的接口收到了保护。

    2.获取Token

    调用模拟登陆授权接口:http://localhost:5000/api/Auth?userName=zhangsan&pwd=123

    这里的用户密码是随便写的,因为我们模拟登陆只是校验了下非空,因此写什么都能通过

    成功得到了响应

    然后我们得到了一个xxx.yyy.zzz 格式的 token 值。我们把token复制出来

    3.在刚才401的接口请求HEADER中添加JWT的参数,把我们的token加上去

    再次调用我们的模拟数据接口,但是这次我们加了一个HEADER:http://localhost:5000/api/value2

    把内容粘出来

    User-Agent: Fiddler
    Host: localhost:5000
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOiIxNTYwMzQ1MDIxIiwiZXhwIjoxNTYwMzQ2ODIxLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiemhhbmdzYW4iLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAifQ.x7Slk4ho1hZc8sR8_McVTB6VEYLz_v-5eaHvXtIDS-o

    这里需要注意Bearer 后面是有一个空格的,然后就是我们上一步获取到的token

    嗯,没有401了,成功返回了数据

    4.JWT的Token过期

    我们且倒一杯开水,坐等30分钟(我们代码中设置的过期时间),然后再次调用数据接口:http://localhost:5000/api/value2

    又变成了401,我们看下详细的返回数据

    这里有标注,错误描述 token过期,说明我们设置的token过期时间生效了

    【结束】

    到这里,我们JWT的简介以及asp.net core 集成JWT已经完美完成,当然了这只是一个demo,在实际的应用中需要补充和完善的地方还有很多。

    如果想要完整项目源码的,可以参考地址:https://github.com/sevenTiny/Demo.Jwt

    如果有幸能帮助到你,高抬贵手点个star吧~

    总结

    下载本文
    显示全文
    专题