查看原文
其他

ASP.NET Core 集成 JWT

DotNet 2020-09-12

(给DotNet加星标,提升.Net技能


转自:7tiny
cnblogs.com/7tiny/p/11012035.html

什么是JWT


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


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



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


JWT有什么优势?


先看我们传统的身份校验方式


  1. 用户向服务器发送用户名和密码。


  2. 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。


  3. 服务器向用户返回一个 session_id,写入用户的 Cookie。


  4. 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。


  5. 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。


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


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



1、Header 头


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


例如:


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


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



除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。例如:


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



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





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




添加数据访问模拟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






成功得到了响应





把内容粘出来


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过期时间生效了


5、JWT添加自定义的参数(比如带上用户信息)


假如我们想在认证通过的时候,直接从jwt的token中获取到登陆的用户名,该怎么操作呢?


首先在我们的获取token 的api接口里面添加一个Claim节点,key可以随便给,也可以使用已经提供好的一些预置Key,value是我们登陆的userName(仅作为演示)



然后在我们的模拟数据接口获取自定义参数



这里使用HttpContext的授权扩展方法,拿到认证的信息,我们来看下结果




请求成功返回,并且也拿到了我们一开始写入的userName


结束


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


完整项目源码地址:https://github.com/sevenTiny/Demo.Jwt


推荐阅读

(点击标题可跳转阅读)

ASP.NET Core基于JWT的认证

ASP.NET Core基于JWT的认证和授权

.NET Core API 框架实现接口的JWT授权验证


看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

好文章,我在看❤️

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存