用JWT來保護我們的ASP.NET Core Web API__.net

來源:互聯網
上載者:User

   在上一篇部落格中,自己動手寫了一個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

的方法。

 1         /// <summary> 2         /// invoke the middleware 3         /// </summary> 4         /// <param name="context"></param> 5         /// <returns></returns> 6         public async Task Invoke(HttpContext context) 7         {            8             if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal)) 9             {10                 await _next(context);11             }12 13             // Request must be POST with Content-Type: application/x-www-form-urlencoded14             if (!context.Request.Method.Equals("POST")15                || !context.Request.HasFormContentType)16             {17                 await ReturnBadRequest(context);             18             }19             await GenerateAuthorizedResult(context);20         }

 

  Invoke方法其實是不用多說的,不過我們這裡是做了一個控制,只接收POST請求,並且是只接收以表單形式提交的資料,GET的請求和其

他contenttype類型是屬於非法的請求,會返回bad request的狀態。

  下面說說授權中比較重要的東西,access_token的產生。

 1         /// <summary> 2         /// get the jwt 3         /// </summary> 4         /// <param name="username"></param> 5         /// <returns></returns> 6         private string GetJwt(string username) 7         { 8             var now = DateTime.UtcNow; 9 10             var claims = new Claim[]11             {12                 new Claim(JwtRegisteredClaimNames.Sub, username),13                 new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),14                 new Claim(JwtRegisteredClaimNames.Iat, now.ToUniversalTime().ToString(),15                           ClaimValueTypes.Integer64)16             };17 18             var jwt = new JwtSecurityToken(19                 issuer: _options.Issuer,20                 audience: _options.Audience,21                 claims: claims,22                 notBefore: now,23                 expires: now.Add(_options.Expiration),24                 signingCredentials: _options.SigningCredentials);25             var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);26 27             var response = new28             {29                 access_token = encodedJwt,30                 expires_in = (int)_options.Expiration.TotalSeconds,31                 token_type = "Bearer"32             };   33             return JsonConvert.SerializeObject(response, new JsonSerializerSettings { Formatting = Formatting.Indented });34         }
  

  claims包含了多個claim,你想要那幾個,可以根據自己的需要來添加,JwtRegisteredClaimNames是一個結構體,裡麵包含了所有的可選項。

 1     public struct JwtRegisteredClaimNames 2     { 3         public const string Acr = "acr"; 4         public const string Actort = "actort"; 5         public const string Amr = "amr"; 6         public const string AtHash = "at_hash"; 7         public const string Aud = "aud"; 8         public const string AuthTime = "auth_time"; 9         public const string Azp = "azp";10         public const string Birthdate = "birthdate";11         public const string CHash = "c_hash";12         public const string Email = "email";13         public const string Exp = "exp";14         public const string FamilyName = "family_name";15         public const string Gender = "gender";16         public const string GivenName = "given_name";17         public const string Iat = "iat";18         public const string Iss = "iss";19         public const string Jti = "jti";20         public const string NameId = "nameid";21         public const string Nbf = "nbf";22         public const string Nonce = "nonce";23         public const string Prn = "prn";24         public const string Sid = "sid";25         public const string Sub = "sub";26         public const string Typ = "typ";27         public const string UniqueName = "unique_name";28         public const string Website = "website";29     }
JwtRegisteredClaimNames

還需要一個JwtSecurityToken對象,這個對象是至關重要的。有了時間、Claims和JwtSecurityToken對象,只要調用JwtSecurityTokenHandler

的WriteToken就可以得到類似這樣的一個加密之後的字串,這個字串由3部分組成用‘.’分隔。每部分代表什麼可以去官網尋找。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

  最後我們要用json的形式返回這個access_token、access_token的有效時間和一些其他的資訊。

  還需要在Startup的Configure方法中去調用我們的中介軟體。

 1             var audienceConfig = Configuration.GetSection("Audience"); 2             var symmetricKeyAsBase64 = audienceConfig["Secret"]; 3             var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64); 4             var signingKey = new SymmetricSecurityKey(keyByteArray); 5  6             app.UseTokenProvider(new TokenProviderOptions 7             { 8                 Audience = "Catcher Wong", 9                 Issuer = "http://catcher1994.cnblogs.com/",10                 SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),11             });

 

  到這裡,我們的授權服務網站已經是做好了。下面就編寫幾個單元測試來驗證一下這個授權。

  測試一:授權服務網站能產生正確的jwt。

 1         [Fact] 2         public async Task authorized_server_should_generate_token_success() 3         { 4             //arrange 5             var data = new Dictionary<string, string>(); 6             data.Add("username", "Member"); 7             data.Add("password", "123"); 8             HttpContent ct = new FormUrlEncodedContent(data); 9 10             //act11             System.Net.Http.HttpResponseMessage message_token = await _client.PostAsync("http://127.0.0.1:8000/auth/token", ct);12             string res = await message_token.Content.ReadAsStringAsync();13             var obj = Newtonsoft.Json.JsonConvert.DeserializeObject<Token>(res);14 15             //assert16             Assert.NotNull(obj);17             Assert.Equal("600", obj.expires_in);18             Assert.Equal(3, obj.access_token.Split('.').Length);19             Assert.Equal("Bearer", obj.token_type);20         }

 

  測試二:授權服務網站因為使用者名稱或密碼不正確導致不能產生正確的jwt。

 1         [Fact] 2         public async Task authorized_server_should_generate_token_fault_by_invalid_app() 3         { 4             //arrange 5             var data = new Dictionary<string, string>(); 6             data.Add("username", "Member"); 7             data.Add("password", "123456"); 8             HttpContent ct = new FormUrlEncodedContent(data); 9 10             //act11             System.Net.Http.HttpResponseMessage message_token = await _client.PostAsync("http://127.0.0.1:8000/auth/token", ct);12             var res = await message_token.Content.ReadAsStringAsync();13             dynamic obj = Newtonsoft.Json.JsonConvert.DeserializeObject(res);14 15             //assert16             Assert.Equal("invalid_grant", (string)obj.error);17             Assert.Equal(HttpStatusCode.BadRequest, message_token.StatusCode);18         }

 

  測試三:授權服務網站因為不是發起post請求導致不能產生正確的jwt。

 1         [Fact] 2         public async Task authorized_server_should_generate_token_fault_by_invalid_httpmethod() 3         { 4             //arrange 5             Uri uri = new Uri("http://127.0.0.1:8000/auth/token?username=Member&password=123456"); 6  7             //act 8             System.Net.Http.HttpResponseMessage message_token = await _client.GetAsync(uri); 9             var res = await message_token.Content.ReadAsStringAsync();10             dynamic obj = Newtonsoft.Json.JsonConvert.DeserializeObject(res);11 12             //assert13             Assert.Equal("invalid_grant", (string)obj.error);14             Assert.Equal(HttpStatusCode.BadRequest, message_token.StatusCode);15         }

    再來看看測試的結果:       都通過了。

  斷點拿一個access_token去http://jwt.calebb.net/ 解密看看

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJNZW1iZXIiLCJqdGkiOiI2MzI1MmE1My0yMjY5LTQ4YzEtYmQwNi1lOWRiMzdmMTRmYTQiLCJpYXQiOiIyMDE2LzExLzEyIDI6NDg6MTciLCJuYmYiOjE0Nzg5MTg4OTcsImV4cCI6MTQ3ODkxOTQ5NywiaXNzIjoiaHR0cDovL2NhdGNoZXIxOTk0LmNuYmxvZ3MuY29tLyIsImF1ZCI6IkNhdGNoZXIgV29uZyJ9.Cu2vTJ4JAHgbJGzwv2jCmvz17HcyOsRnTjkTIEA0EbQ

  下面就是API的開發了。

  這裡是直接用了建立API項目產生的ValueController作為示範,畢竟跟ASP.NET Web API是

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.