在用Middleware给ASP.NET Core Web API添加自己的授权验证 中,自己动手写了一个Middleware来处理API的授权验证,现在就采用另外一种方式来处理这个授权验证的问题,毕竟现在也有不少开源的东西可以用,今天用的是JWT。

  什么是JWT呢?JWT的全称是JSON WEB TOKENS,是一种自包含令牌格式。官方网址:https://jwt.io/,或多或少应该都有听过这个。

  先来看看下面的两个图:

  站点是通过RPC的方式来访问api取得资源的,当站点是直接访问api,没有拿到有访问权限的令牌,那么站点是拿不到相关的数据资源的。

就像左图展示的那样,发起了请求但是拿不到想要的结果;当站点先去授权服务器拿到了可以访问api的access_token(令牌)后,再通过这个

access_token去访问api,api才会返回受保护的数据资源。

  这个就是基于令牌验证的大致流程了。可以看出授权服务器占着一个很重要的地位。

  下面先来看看授权服务器做了些什么并如何来实现一个简单的授权。

  做了什么?授权服务器在整个过程中的作用是:接收客户端发起申请access_token的请求,并校验其身份的合法性,最终返回一个包含

access_token的json字符串。

  如何实现?我们还是离不开中间件这个东西。这次我们写了一个TokenProviderMiddleware,主要是看看invoke方法和生成access_token

的方法。

/// <summary>

/// invoke the middleware

/// </summary>

/// <param name="context"></param>

/// <returns></returns>

public async Task Invoke(HttpContext context)

{

if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal))

{

await _next(context);

}

// Request must be POST with Content-Type: application/x-www-form-urlencoded

if (!context.Request.Method.Equals("POST")

|| !context.Request.HasFormContentType)

{

await ReturnBadRequest(context);

}

await GenerateAuthorizedResult(context);

}

  Invoke方法其实是不用多说的,不过我们这里是做了一个控制,只接收POST请求,并且是只接收以表单形式提交的数据,GET的请求和其

他contenttype类型是属于非法的请求,会返回bad request的状态。

  下面说说授权中比较重要的东西,access_token的生成。

/// <summary>

/// get the jwt

/// </summary>

/// <param name="username"></param>

/// <returns></returns>

private string GetJwt(string username)

{

var now = DateTime.UtcNow;

var claims = new Claim[]

{

new Claim(JwtRegisteredClaimNames.Sub, username),

new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),

new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(),

ClaimValueTypes.Integer64)

};

var jwt = new JwtSecurityToken(

issuer: _options.Issuer,

audience: _options.Audience,

claims: claims,

notBefore: now,

expires: now.Add(_options.Expiration),

signingCredentials: _options.SigningCredentials);

var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);

var response = new

{

access_token = encodedJwt,

expires_in = (int)_options.Expiration.TotalSeconds,

token_type = "Bearer"

};

return JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented });

}

 

  claims包含了多个claim,你想要那几个,可以根据自己的需要来添加,JwtRegisteredClaimNames是一个结构体,里面包含了所有的可选项。

public struct JwtRegisteredClaimNames

{

public const string Acr = "acr";

public const string Actort = "actort";

public const string Amr = "amr";

public const string AtHash = "at_hash";

public const string Aud = "aud";

public const string AuthTime = "auth_time";

public const string Azp = "azp";

public const string Birthdate = "birthdate";

public const string CHash = "c_hash";

public const string Email = "email";

public const string Exp = "exp";

public const string FamilyName = "family_name";

public const string Gender = "gender";

public const string GivenName = "given_name";

public const string Iat = "iat";

public const string Iss = "iss";

public const string Jti = "jti";

public const string NameId = "nameid";

public const string Nbf = "nbf";

public const string Nonce = "nonce";

public const string Prn = "prn";

public const string Sid = "sid";

public const string Sub = "sub";

public const string Typ = "typ";

public const string UniqueName = "unique_name";

public const string Website = "website";

}

JwtRegisteredClaimNames

还需要一个JwtSecurityToken对象,这个对象是至关重要的。有了时间、Claims和JwtSecurityToken对象,只要调用JwtSecurityTokenHandler

的WriteToken就可以得到类似这样的一个加密之后的字符串,这个字符串由3部分组成用‘.’分隔。每部分代表什么可以去官网查找。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

  最后我们要用json的形式返回这个access_token、access_token的有效时间和一些其他的信息。

  还需要在Startup的Configure方法中去调用我们的中间件。

var audienceConfig = Configuration.GetSection("Audience");

var symmetricKeyAsBase64 = audienceConfig["Secret"];

var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);

var signingKey = new SymmetricSecurityKey(keyByteArray);

app.UseTokenProvider(new TokenProviderOptions

{

Audience = "Catcher Wong",

Issuer = "http://catcher1994.cnblogs.com/",

SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),

});

  到这里,我们的授权服务站点已经是做好了。下面就编写几个单元测试来验证一下这个授权。

  测试一:授权服务站点能生成正确的jwt。

[Fact]

public async Task authorized_server_should_generate_token_success()

{

//arrange

var data = new Dictionary<string, string>();

data.Add("username", "Member");

data.Add("password", "123");

HttpContent ct = new FormUrlEncodedContent(data);

//act

System.Net.Http.HttpResponseMessage message_token = await _client.PostAsync("http://127.0.0.1:8000/auth/token", ct);

string res = await message_token.Content.ReadAsStringAsync();

var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<Token>(res);

//assert

Assert.NotNull(obj);

Assert.Equal("600", obj.expires_in);

Assert.Equal(3, obj.access_token.Split('.').Length);

Assert.Equal("Bearer", obj.token_type);

}

  测试二:授权服务站点因为用户名或密码不正确导致不能生成正确的jwt。

[Fact]

public async Task authorized_server_should_generate_token_fault_by_invalid_app()

{

//arrange

var data = new Dictionary<string, string>();

data.Add("username", "Member");

data.Add("password", "123456");

HttpContent ct = new FormUrlEncodedContent(data);

//act

System.Net.Http.HttpResponseMessage message_token = await _client.PostAsync("http://127.0.0.1:8000/auth/token", ct);

var res = await message_token.Content.ReadAsStringAsync();

dynamic obj = Newtonsoft.Json.JsonConvert.DeserializeObject(res);

//assert

Assert.Equal("invalid_grant", (string)obj.error);

Assert.Equal(HttpStatusCode.BadRequest, message_token.StatusCode);

}

  测试三:授权服务站点因为不是发起post请求导致不能生成正确的jwt。

Fact]

public async Task authorized_server_should_generate_token_fault_by_invalid_httpmethod()

{

//arrange

Uri uri = new Uri("http://127.0.0.1:8000/auth/token?username=Member&password=123456");

//act

System.Net.Http.HttpResponseMessage message_token = await _client.GetAsync(uri);

var res = await message_token.Content.ReadAsStringAsync();

dynamic obj = Newtonsoft.Json.JsonConvert.DeserializeObject(res);

//assert

Assert.Equal("invalid_grant", (string)obj.error);

Assert.Equal(HttpStatusCode.BadRequest, message_token.StatusCode);

}

  再来看看测试的结果:

  

  都通过了。

  断点拿一个access_token去http://jwt.calebb.net/ 解密看看

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJNZW1iZXIiLCJqdGkiOiI2MzI1MmE1My0yMjY5LTQ4YzEtYmQwNi1lOWRiMzdmMTRmYTQiLCJpYXQiOiIyMDE2LzExLzEyIDI6NDg6MTciLCJuYmYiOjE0Nzg5MTg4OTcsImV4cCI6MTQ3ODkxOTQ5NywiaXNzIjoiaHR0cDovL2NhdGNoZXIxOTk0LmNuYmxvZ3MuY29tLyIsImF1ZCI6IkNhdGNoZXIgV29uZyJ9.Cu2vTJ4JAHgbJGzwv2jCmvz17HcyOsRnTjkTIEA0EbQ

  下面就是API的开发了。

  这里是直接用了新建API项目生成的ValueController作为演示,毕竟跟ASP.NET Web API是大同小异的。这里的重点是配置

JwtBearerAuthentication,这里是不用我们再写一个中间件了,我们是定义好要用的Option然后直接用JwtBearerAuthentication就可以了。

public void ConfigureJwtAuth(IApplicationBuilder app)

{

var audienceConfig = Configuration.GetSection("Audience");

var symmetricKeyAsBase64 = audienceConfig["Secret"];

var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);

var signingKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(keyByteArray);

var tokenValidationParameters = new TokenValidationParameters

{

// The signing key must match!

ValidateIssuerSigningKey = true,

IssuerSigningKey = signingKey,

// Validate the JWT Issuer (iss) claim

ValidateIssuer = true,

ValidIssuer = "http://catcher1994.cnblogs.com/",

// Validate the JWT Audience (aud) claim

ValidateAudience = true,

ValidAudience = "Catcher Wong",

// Validate the token expiry

ValidateLifetime = true,

ClockSkew = TimeSpan.Zero

};

app.UseJwtBearerAuthentication(new JwtBearerOptions

{

AutomaticAuthenticate = true,

AutomaticChallenge = true,

TokenValidationParameters = tokenValidationParameters,

});

}

  然后在Startup的Configure中调用上面的方法即可。

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)

{

loggerFactory.AddConsole(Configuration.GetSection("Logging"));

loggerFactory.AddDebug();

ConfigureJwtAuth(app);

app.UseMvc();

}

  到这里之后,大部分的工作是已经完成了,还有最重要的一步,在想要保护的api上加上Authorize这个Attribute,这样Get这个方法就会要

求有access_token才会返回结果,不然就会返回401。这是在单个方法上的,也可以在整个控制器上面添加这个Attribute,这样控制器里面的方

法就都会受到保护。

// GET api/values/5

[HttpGet("{id}")]

[Authorize]

public string Get(int id)

{

return "value";

}

  OK,同样编写几个单元测试验证一下。

  测试一:valueapi在没有授权的请求会返回401状态。

[Fact]

public void value_api_should_return_unauthorized_without_auth()

{

//act

HttpResponseMessage message = _client.GetAsync("http://localhost:63324/api/values/1").Result;

string result = message.Content.ReadAsStringAsync().Result;

//assert

Assert.False(message.IsSuccessStatusCode);

Assert.Equal(HttpStatusCode.Unauthorized,message.StatusCode);

Assert.Empty(result);

}

  

   测试二:valueapi请求没有[Authorize]标记的方法时能正常返回结果。

[Fact]

public void value_api_should_return_result_without_authorize_attribute()

{

//act

HttpResponseMessage message = _client.GetAsync("http://localhost:63324/api/values").Result;

string result = message.Content.ReadAsStringAsync().Result;

var res = Newtonsoft.Json.JsonConvert.DeserializeObject<string[]>(result);

//assert

Assert.True(message.IsSuccessStatusCode);

Assert.Equal(2, res.Length);

}

   测试三:valueapi在授权的请求中会返回正确的结果。

[Fact]

public void value_api_should_success_by_valid_auth()

{

//arrange

var data = new Dictionary<string, string>();

data.Add("username", "Member");

data.Add("password", "123");

HttpContent ct = new FormUrlEncodedContent(data);

//act

var obj = GetAccessToken(ct);

_client.DefaultRequestHeaders.Add("Authorization", "Bearer " + obj.access_token);

HttpResponseMessage message = _client.GetAsync("http://localhost:63324/api/values/1").Result;

string result = message.Content.ReadAsStringAsync().Result;

//assert

Assert.True(message.IsSuccessStatusCode);

Assert.Equal(3, obj.access_token.Split('.').Length);

Assert.Equal("value",result);

}

  再来看看测试的结果:

  

  测试通过。

  再通过浏览器直接访问那个受保护的方法。响应头就会提示www-authenticate:Bearer,这个是身份验证的质询,告诉客户端必须要提供相

应的身份验证才能访问这个资源(api)。

  

  这也是为什么在单元测试中会添加一个Header的原因,正常的使用也是要在请求的报文头中加上这个。

   _client.DefaultRequestHeaders.Add("Authorization", "Bearer " + obj.access_token);

  其实看一下源码,更快知道为什么。JwtBearerHandler.cs

  下图是关于头部加Authorization的源码解释。

  

原文地址:http://www.cnblogs.com/catcher1994/p/6057484.html

.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

用JWT来保护我们的ASP.NET Core Web API相关推荐

  1. 保护我方Id | ASP.NET Core Web API使用加密Id

    前言 上次,我们介绍了hashids.net,可以将数值型Id加密成无意义的字符串,但是通过这些字符串又可以反向映射出真实的Id以供内部使用.比如B站的播放链接https://www.bilibili ...

  2. 【壹刊】Azure AD 保护的 ASP.NET Core Web API (下)

    一,引言 上一节讲到如何在我们的项目中集成Azure AD 保护我们的API资源,以及在项目中集成Swagger,并且如何把Swagger作为一个客户端进行认证和授权去访问我们的WebApi资源的?本 ...

  3. ASP.NET Core Web API 与 SSL

    SSL 一直没有真正研究过SSL,不知道下面的理解是否正确. SSL是Secure Sockets Layer的缩写,它用来保护服务器和客户端之前的通信.它是基于信任+加密的概念. 在介绍SSL的原理 ...

  4. ASP.NET Core Web API下事件驱动型架构的实现(四):CQRS架构中聚合与聚合根的实现

    在前面两篇文章中,我详细介绍了基本事件系统的实现,包括事件派发和订阅.通过事件处理器执行上下文来解决对象生命周期问题,以及一个基于RabbitMQ的事件总线的实现.接下来对于事件驱动型架构的讨论,就需 ...

  5. ASP.NET Core Web API 索引 (更新Identity Server 4 视频教程)

    GraphQL 使用ASP.NET Core开发GraphQL服务器 -- 预备知识(上) 使用ASP.NET Core开发GraphQL服务器 -- 预备知识(下) [视频] 使用ASP.NET C ...

  6. 如何测试ASP.NET Core Web API

    在本文中,我们将研究如何测试你的ASP .NET Core 2.0 Web API解决方案.我们将了解使用单元测试进行内部测试,使用全新的ASP .NET Core的集成测试框架来进行外部测试. 本文 ...

  7. 针对ASP.NET Core Web API的先进架构

    \ 本点要点 \\ 与传统的ASP.NET相比,ASP.NET Core的新架构提供了一些好处\\t ASP.NET Core从一开始就包含对依赖注入的支持\\t 单一职责原则简化了实施和设计.\\t ...

  8. ASP.NET Core Web APi获取原始请求内容

    前言 我们讲过ASP.NET Core Web APi路由绑定,本节我们来讲讲如何获取客户端请求过来的内容. ASP.NET Core Web APi捕获Request.Body内容 [HttpPos ...

  9. 重温.NET下Assembly的加载过程 ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线...

    重温.NET下Assembly的加载过程 最近在工作中牵涉到了.NET下的一个古老的问题:Assembly的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后发现 ...

最新文章

  1. 转 Android开发学习笔记:浅谈WebView
  2. 合种樟子松/华山松专车3天领证
  3. SpringMVC异常处理机制详解[附带源码分析]
  4. jenkins 打包 命令
  5. Android子线程中更新UI的4种方法
  6. 白话tensorflow分布式部署和开发
  7. BUUCTF-WEB:[HCTF 2018]WarmUp
  8. mysql float 怎么设置长度_MySQL中float double decimal区别总结
  9. python制作手机壁纸_Python爬取手机壁纸图片
  10. USACO1.1.1 - PROB Your Ride Is Here
  11. Egret入门学习日记 --- 第十二篇(书中 5.1节 内容)
  12. python实现diff json 并且打印出log日志
  13. Mac 下通过Homebrew安装ADB工具
  14. linux文件系统F2FS,文件系统F2FS也将造福SSD
  15. 在计算机中怎么找到打字的文件,想在电脑上打字,然后把电脑上写的字,弄到纸上怎么做...
  16. 《迅雷链精品课》第九课:区块链 P2P 网络
  17. SIM868中的GPRS调试失败的几个原因
  18. XCTF-MFW Git泄露,命令执行漏洞 详解
  19. 在Vue中 用trun js + pdf-dist 实现pdf翻页效果
  20. 视频字幕识别(百度AI开放平台OCR | python | opencv)

热门文章

  1. mysql 批量更新和批量插入
  2. XML--使用XML来将字符串分隔成行数据
  3. 做操作系统的公司,为什么不能把系统安全做好?
  4. 有关技术管理的一些思考
  5. 回到地球之后,这个男人创建了Ubuntu
  6. c#winform自定义窗体(含源码)
  7. SingnalR 开发到生产部署闭坑指南
  8. 分布式事务最终一致性-CAP框架轻松搞定
  9. 国产操作系统发展离不开人才和市场
  10. 谁说.NET不适合搞大数据、机器学习和人工智能