用JWT来保护我们的ASP.NET Core Web API
2016-11-13 20:39
961 查看
在上一篇博客中,自己动手写了一个Middleware来处理API的授权验证,现在就采用另外一种方式来处理这个授权验证的问题,毕竟现在也
有不少开源的东西可以用,今天用的是JWT。
什么是JWT呢?JWT的全称是JSON WEB TOKENS,是一种自包含令牌格式。官方网址:https://jwt.io/,或多或少应该都有听过这个。
先来看看下面的两个图:
JwtRegisteredClaimNames
还需要一个JwtSecurityToken对象,这个对象是至关重要的。有了时间、Claims和JwtSecurityToken对象,只要调用JwtSecurityTokenHandler
的WriteToken就可以得到类似这样的一个加密之后的字符串,这个字符串由3部分组成用‘.’分隔。每部分代表什么可以去官网查找。
最后我们要用json的形式返回这个access_token、access_token的有效时间和一些其他的信息。
还需要在Startup的Configure方法中去调用我们的中间件。
到这里,我们的授权服务站点已经是做好了。下面就编写几个单元测试来验证一下这个授权。
测试一:授权服务站点能生成正确的jwt。
测试二:授权服务站点因为用户名或密码不正确导致不能生成正确的jwt。
测试三:授权服务站点因为不是发起post请求导致不能生成正确的jwt。
再来看看测试的结果:
都通过了。
断点拿一个access_token去http://jwt.calebb.net/ 解密看看
下面就是API的开发了。
这里是直接用了新建API项目生成的ValueController作为演示,毕竟跟ASP.NET Web API是大同小异的。这里的重点是配置
JwtBearerAuthentication,这里是不用我们再写一个中间件了,我们是定义好要用的Option然后直接用JwtBearerAuthentication就可以了。
然后在Startup的Configure中调用上面的方法即可。
到这里之后,大部分的工作是已经完成了,还有最重要的一步,在想要保护的api上加上Authorize这个Attribute,这样Get这个方法就会要
求有access_token才会返回结果,不然就会返回401。这是在单个方法上的,也可以在整个控制器上面添加这个Attribute,这样控制器里面的方
法就都会受到保护。
OK,同样编写几个单元测试验证一下。
测试一:valueapi在没有授权的请求会返回401状态。
测试二:valueapi请求没有[Authorize]标记的方法时能正常返回结果。
测试三:valueapi在授权的请求中会返回正确的结果。
再来看看测试的结果:
测试通过。
再通过浏览器直接访问那个受保护的方法。响应头就会提示www-authenticate:Bearer,这个是身份验证的质询,告诉客户端必须要提供相
应的身份验证才能访问这个资源(api)。
这也是为什么在单元测试中会添加一个Header的原因,正常的使用也是要在请求的报文头中加上这个。
_client.DefaultRequestHeaders.Add("Authorization", "Bearer " + obj.access_token);
其实看一下源码,更快知道为什么。JwtBearerHandler.cs
下图是关于头部加Authorization的源码解释。
JwtBearer的源码:Microsoft.AspNetCore.Authentication.JwtBearer
本文的示例代码:JWTTokenDemo
Thanks for your reading!!!
有不少开源的东西可以用,今天用的是JWT。
什么是JWT呢?JWT的全称是JSON WEB TOKENS,是一种自包含令牌格式。官方网址:https://jwt.io/,或多或少应该都有听过这个。
先来看看下面的两个图:
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的源码解释。
JwtBearer的源码:Microsoft.AspNetCore.Authentication.JwtBearer
本文的示例代码:JWTTokenDemo
Thanks for your reading!!!
相关文章推荐
- 用JWT来保护我们的ASP.NET Core Web API
- 用JWT来保护我们的ASP.NET Core Web API
- 【译】使用Jwt身份认证保护 Asp.Net Core Web Api
- asp.net core 2.0 web api基于JWT自定义策略授权
- 【译】使用Jwt身份认证保护 Asp.Net Core Web Api
- asp.net core 2.0 web api基于JWT自定义策略授权
- Angular5,使用EF和WEB API进行库存管理的ASP.NET Core CRUD
- 让我们Core在一起:ASP.NET Core & .NET Core
- Asp.Net Core Web Api图片上传(一)集成MongoDB存储实例教程
- ASP.NET Core Web API处理HttpResponseMessage类型返回值的问题
- IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习保护API
- ASP.NET Core 数据保护(Data Protection)中篇
- 基于JWT(Json Web Token)的ASP.NET Web API授权方式
- 从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置
- 把Web Api OData移植到Asp.Net Core(5)-https
- IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习保护API
- 使用angular4和asp.net core 2 web api做个练习项目(二), 这部分都是angular
- 使用angular4和asp.net core 2 web api做个练习项目(三)
- ASP.NET Core 数据保护(Data Protection 集群场景)下篇
- 用VSCode开发一个asp.net core2.0+angular5项目(5): Angular5+asp.net core 2.0 web api文件上传