JWT认证简单介绍

关于Jwt的介绍网上很多,此处不在赘述,我们主要看看jwt的结构。

JWT主要由三部分组成,如下:

HEADER.PAYLOAD.SIGNATURE

HEADER包含token的元数据,主要是加密算法,和签名的类型,如下面的信息,说明了

加密的对象类型是JWT,加密算法是HMAC SHA-256

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

然后需要通过BASE64编码后存入token中

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9    

Payload主要包含一些声明信息(claim),这些声明是key-value对的数据结构。

通常如用户名,角色等信息,过期日期等,因为是未加密的,所以不建议存放敏感信息。

{"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name":"admin","exp":1578645536,"iss":"webapi.cn","aud":"WebApi"}

也需要通过BASE64编码后存入token中

eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1Nzg2NDU1MzYsImlzcyI6IndlYmFwaS5jbiIsImF1ZCI6IldlYkFwaSJ9

Signaturejwt要符合jws(Json Web Signature)的标准生成一个最终的签名。把编码后的Header和Payload信息加在一起,然后使用一个强加密算法,如 HmacSHA256,进行加密。HS256(BASE64(Header).Base64(Payload),secret)

2_akEH40LR2QWekgjm8Tt3lesSbKtDethmJMo_3jpF4

最后生成的token如下

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1Nzg2NDU1MzYsImlzcyI6IndlYmFwaS5jbiIsImF1ZCI6IldlYkFwaSJ9.2_akEH40LR2QWekgjm8Tt3lesSbKtDethmJMo_3jpF4

开发环境

框架:asp.net 3.1

IDE:VS2019

ASP.NET 3.1 Webapi中使用JWT认证

命令行中执行执行以下命令,创建webapix项目:

dotnet new webapi -n Webapi -o WebApi

特别注意的时,3.x默认是没有jwt的Microsoft.AspNetCore.Authentication.JwtBearer库的,所以需要手动添加NuGet Package,切换到项目所在目录,执行 .net cli命令

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 3.1.0

创建一个简单的POCO类,用来存储签发或者验证jwt时用到的信息

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; namespace Webapi.Models {
public class TokenManagement
{
[JsonProperty("secret")]
public string Secret { get; set; } [JsonProperty("issuer")]
public string Issuer { get; set; } [JsonProperty("audience")]
public string Audience { get; set; } [JsonProperty("accessExpiration")]
public int AccessExpiration { get; set; } [JsonProperty("refreshExpiration")]
public int RefreshExpiration { get; set; }
}
}

然后在 appsettings.Development.json 增加jwt使用到的配置信息(如果是生成环境在appsettings.json添加即可)

"tokenManagement": {
"secret": "123456",
"issuer": "webapi.cn",
"audience": "WebApi",
"accessExpiration": 30,
"refreshExpiration": 60
}

然后再startup类的ConfigureServices方法中增加读取配置信息

public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.Configure<TokenManagement>(Configuration.GetSection("tokenManagement"));
var token = Configuration.GetSection("tokenManagement").Get<TokenManagement>(); }

到目前为止,我们完成了一些基础工作,下面再webapi中注入jwt的验证服务,并在中间件管道中启用authentication中间件。

startup类中要引用jwt验证服务的命名空间

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;

然后在ConfigureServices方法中添加如下逻辑

services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,
ValidAudience = token.Audience,
ValidateIssuer = false,
ValidateAudience = false
};
});

Configure方法中启用验证

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
} app.UseHttpsRedirection(); app.UseAuthentication();
app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}

上面完成了JWT验证的功能,下面就需要增加签发token的逻辑。我们需要增加一个专门用来用户认证和签发token的控制器,命名成AuthenticationController,同时增加一个请求的DTO类

public class LoginRequestDTO
{
[Required]
[JsonProperty("username")]
public string Username { get; set; } [Required]
[JsonProperty("password")]
public string Password { get; set; }
}
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ControllerBase
{
[AllowAnonymous]
[HttpPost, Route("requestToken")]
public ActionResult RequestToken([FromBody] LoginRequestDTO request)
{
if (!ModelState.IsValid)
{
return BadRequest("Invalid Request");
} return Ok(); }
}

目前上面的控制器只实现了基本的逻辑,下面我们要创建签发token的服务,去完成具体的业务。第一步我们先创建对应的服务接口,命名为IAuthenticateService

public interface IAuthenticateService
{
bool IsAuthenticated(LoginRequestDTO request, out string token);
}

接下来,实现接口

public class TokenAuthenticationService : IAuthenticateService
{
public bool IsAuthenticated(LoginRequestDTO request, out string token)
{
throw new NotImplementedException();
}
}

StartupConfigureServices方法中注册服务

services.AddScoped<IAuthenticateService, TokenAuthenticationService>();

在Controller中注入IAuthenticateService服务,并完善action

public class AuthenticationController : ControllerBase
{
private readonly IAuthenticateService _authService;
public AuthenticationController(IAuthenticateService authService)
{
this._authService = authService;
}
[AllowAnonymous]
[HttpPost, Route("requestToken")]
public ActionResult RequestToken([FromBody] LoginRequestDTO request)
{
if (!ModelState.IsValid)
{
return BadRequest("Invalid Request");
} string token;
if (_authService.IsAuthenticated(request, out token))
{
return Ok(token);
} return BadRequest("Invalid Request"); }
}

正常情况,我们都会根据请求的用户和密码去验证用户是否合法,需要连接到数据库获取数据进行校验,我们这里为了方便,假设任何请求的用户都是合法的。

这里单独加个用户管理的服务,不在IAuthenticateService这个服务里面添加相应逻辑,主要遵循了职责单一原则。首先和上面一样,创建一个服务接口IUserService

public interface IUserService
{
bool IsValid(LoginRequestDTO req);
}

实现IUserService接口

public class UserService : IUserService
{
//模拟测试,默认都是人为验证有效
public bool IsValid(LoginRequestDTO req)
{
return true;
}
}

同样注册到容器中

services.AddScoped<IUserService, UserService>();

接下来,就要完善TokenAuthenticationService签发token的逻辑,首先要注入IUserService 和 TokenManagement,然后实现具体的业务逻辑,这个token的生成还是使用的Jwt的类库提供的api,具体不详细描述。

特别注意下TokenManagement的注入是已IOptions的接口类型注入的,还记得在Startpup中吗?我们是通过配置项的方式注册TokenManagement类型的。

 public class TokenAuthenticationService : IAuthenticateService
{
private readonly IUserService _userService;
private readonly TokenManagement _tokenManagement;
public TokenAuthenticationService(IUserService userService, IOptions<TokenManagement> tokenManagement)
{
_userService = userService;
_tokenManagement = tokenManagement.Value;
}
public bool IsAuthenticated(LoginRequestDTO request, out string token)
{
token = string.Empty;
if (!_userService.IsValid(request))
return false;
var claims = new[]
{
new Claim(ClaimTypes.Name,request.Username)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenManagement.Secret));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var jwtToken = new JwtSecurityToken(_tokenManagement.Issuer, _tokenManagement.Audience, claims, expires: DateTime.Now.AddMinutes(_tokenManagement.AccessExpiration), signingCredentials: credentials); token = new JwtSecurityTokenHandler().WriteToken(jwtToken); return true; }
}

准备好测试试用的APi,打上Authorize特性,表明需要授权!

[ApiController]
[Route("[controller]")]
[Authorize]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
}; private readonly ILogger<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
} [HttpGet]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}

支持我们可以测试验证了,我们可以使用postman来进行http请求,先启动http服务,获取url,先测试一个访问需要授权的接口,但没有携带token信息,返回是401,表示未授权

下面我们先通过认证接口,获取token,居然报错,查询了下,发现HS256算法的秘钥长度最新为128位,转换成字符至少16字符,之前设置的秘钥是123456,所以导致异常。

System.ArgumentOutOfRangeException: IDX10603: Decryption failed. Keys tried: 'HS256'. Exceptions caught: '128'. token: '48' (Parameter 'KeySize') at

更新秘钥

 "tokenManagement": {
"secret": "123456123456123456",
"issuer": "webapi.cn",
"audience": "WebApi",
"accessExpiration": 30,
"refreshExpiration": 60
}

重新发起请求,成功获取token

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1Nzg2NDUyMDMsImlzcyI6IndlYmFwaS5jbiIsImF1ZCI6IldlYkFwaSJ9.AehD8WTAnEtklof2OJsvg0U4_o8_SjdxmwUjzAiuI-o

把token带到之前请求的api中,重新测试,成功获取数据

总结

基于token的认证方式,让我们构建分布式/松耦合的系统更加容易。任何地方生成的token,只有拥有相同秘钥,就可以再任何地方进行签名校验。

当然要用好jwt认证方式,还有其他安全细节需要处理,比如palyload中不能存放敏感信息,使用https的加密传输方式等等,可以根据业务实际需要再进一步安全加固!

同时我们也发现使用token,就可以摆脱cookie的限制,所以JWT是移动app开发的首选!

ASP.Net Core 3.0 中使用JWT认证的更多相关文章

  1. asp.net core 3.0 中使用 swagger

    asp.net core 3.0 中使用 swagger Intro 上次更新了 asp.net core 3.0 简单的记录了一下 swagger 的使用,那个项目的 api 比较简单,都是匿名接口 ...

  2. 避免在ASP.NET Core 3.0中为启动类注入服务

    本篇是如何升级到ASP.NET Core 3.0系列文章的第二篇. Part 1 - 将.NET Standard 2.0类库转换为.NET Core 3.0类库 Part 2 - IHostingE ...

  3. ASP.NET Core 1.0 中的依赖项管理

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

  4. 在ASP.NET Core 1.0中如何发送邮件

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:目前.NET Core 1.0中并没有提供SMTP相关的类库,那么要如何从ASP.NE ...

  5. ASP.NET Core 1.0 中使用 Swagger 生成文档

    github:https://github.com/domaindrivendev/Ahoy 之前文章有介绍在ASP.NET WebAPI 中使用Swagger生成文档,ASP.NET Core 1. ...

  6. 用ASP.NET Core 1.0中实现邮件发送功能

    准备将一些项目迁移到 asp.net core 先从封装类库入手,在遇到邮件发送类时发现在 asp.net core 1.0中并示提供SMTP相关类库,于是网上一搜发现了MailKit 好东西一定要试 ...

  7. 在ASP.NET Core 2.0中使用CookieAuthentication

    在ASP.NET Core中关于Security有两个容易混淆的概念一个是Authentication(认证),一个是Authorization(授权).而前者是确定用户是谁的过程,后者是围绕着他们允 ...

  8. 如何在ASP.NET Core 2.0中使用Razor页面

    如何在ASP.NET Core 2.0中使用Razor页面  DotNetCore2017-11-22 14:49 问题 如何在ASP.NET Core 2.0中使用Razor页面 解 创建一个空的项 ...

  9. ASP.NET Core 3.0中使用动态控制器路由

    原文:Dynamic controller routing in ASP.NET Core 3.0 作者:Filip W 译文:https://www.cnblogs.com/lwqlun/p/114 ...

随机推荐

  1. [C#] 用ServiceStack读写redis的性能问题

    ServiceStack.Redis有个方法叫 AddRangeToList,这个方法是有性能问题的.这个方法的实现代码如下: public void AddRangeToList(string li ...

  2. 手动实现如何从H264流中提取SPS/PPS信息

    1,代码比较简单,可以直接用了.流的第一个NALU一定是SPS void get_sps_pps_nalu(uint8_t *data, int len, std::vector<uint8_t ...

  3. 9-6 UVa 11400

    参考 http://www.cnblogs.com/Kiraa/p/5510757.html http://www.cnblogs.com/zhaopAC/p/5159950.html 根据题目说明中 ...

  4. Spring Boot实战之单元测试

    Spring Boot实战之单元测试 本文介绍使用Spring测试框架提供的MockMvc对象,对Restful API进行单元测试 Spring测试框架提供MockMvc对象,可以在不需要客户端-服 ...

  5. 【GYM102091】2018-2019 ACM-ICPC, Asia Nakhon Pathom Regional Contest

    A-Evolution Game 题目大意:有$n$个不同的野兽,定义第$i$ 个野兽有 $i$ 个眼睛和 $h[i]$ 个角,你可以任意从中选择一个野兽进行进化,每次进化角数量必须增加,而且进化后要 ...

  6. dotnet 手动解决 json 解析中不合法字符串

    如果使用 Newtonsoft Json 解析字符串,字符串里面有不清真的格式,那么默认的解析将会炸掉.如果想要自己解决字符串中的不清真格式,可以使用传入 JsonSerializerSettings ...

  7. Storm使用总结

    Strom安装 Strom启动 ./zkServer.sh start 启动nimbus主节点: nohup bin/storm nimbus >> /dev/null & 启动s ...

  8. vs2019 解决方案加载报错

    1. 如图 解决方案: 1.先关闭vs: 2.把C:/Users/<users name>/AppData/Local/Microsoft/VisualStudio/14.0/Compon ...

  9. CS224n: Natural Language Processing学习准备

    cs224n 斯坦福网址,里面包含讲课视频,ppt,代码,学习完后做一个问答系统 http://web.stanford.edu/class/cs224n/index.html 下载anaconda, ...

  10. IAP升级

    一.IAP原理 1.在正常情况下,程序运行路流程: 和STM32类似,STM8内部闪存(FLASH)地址起始于 0x8000(STM32是0x08000000),一般情况下,程序文件就从此地 址开始写 ...