<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      深入淺出Blazor webassembly 之API服務(wù)端保護(hù)

      受保護(hù) API 項目的思路是:

      調(diào)用方先提交用戶名和密碼 (即憑證) 到登錄接口, 由登錄接口驗證憑證合法性, 如果合法, 返回給調(diào)用方一個Jwt token. 

      以后調(diào)用方訪問API時, 需要將該token 加到 Bearer Http 頭上, 服務(wù)方驗證該 token 是否有效, 如果驗證通過, 將允許其繼續(xù)訪問受控API. 

       

      ===================================
      本文目標(biāo)
      ===================================

      1. 實(shí)現(xiàn)一個未受保護(hù)的API

      2. 網(wǎng)站開啟 CORS 跨域共享

      3. 實(shí)現(xiàn)一個受保護(hù)的API

      4. 實(shí)現(xiàn)一個密碼hash的接口(測試用)

      5. 實(shí)現(xiàn)一個登錄接口

       

       

      ===================================
      目標(biāo)1:  實(shí)現(xiàn)一個未受保護(hù)的API
      ===================================

      VS創(chuàng)建一個ASP.net core Host的Blazor wsam解決方案,其中 Server端項目即包含了未受保護(hù)的 WeatherForecast API接口. 

      稍微講解一下 ASP.Net Core API的路由規(guī)則. 

      下面代碼是模板自動生成的,  Route 注解中的參數(shù)是 [controller], HttpGet 注解沒帶參數(shù), 則該方法的url為 http://site/WeatherForecast, 

       

       
      VS 插件 Rest Client 訪問的指令為: 
      GET http://localhost:5223/WeatherForecast HTTP/1.1
      content-type: application/json

       

      稍微調(diào)整一下Route和HttpGet注解參數(shù), 

       

       VS 插件 Rest Client 訪問的指令需要調(diào)整為: 

      GET http://localhost:5223/api/WeatherForecast/list HTTP/1.1
      content-type: application/json

       

      ===================================
      目標(biāo)2:  API網(wǎng)站開啟 CORS 跨域共享
      ===================================

      默認(rèn)情況下, 瀏覽器安全性是不允許網(wǎng)頁向其他域名發(fā)送請求, 這種約束稱為同源策略.  需要說明的是, 同源策略是瀏覽器端的安全管控, 但要解決卻需要改造服務(wù)端. 

      究其原因, 需要了解瀏覽器同源策略安全管控的機(jī)制,  瀏覽器在向其他域名發(fā)送請求時候, 其實(shí)并沒有做額外的管控, 管控發(fā)生在瀏覽器收到其他域名請求結(jié)果時, 瀏覽器會檢查返回結(jié)果中,  如果結(jié)果包含CORS共享標(biāo)識的話, 瀏覽器端也會通過檢查, 如果不包含, 瀏覽器會拋出訪問失敗. 

      VS創(chuàng)建一個ASP.net core Host的Blazor wsam解決方案, wasm是托管ASP.net core 服務(wù)器端網(wǎng)站之內(nèi),  所以不會違反瀏覽器的同源策略約束. 模板項目中, 并沒有開啟CORS共享控制的代碼

      一般情況下, 我們要將blazor wasm獨(dú)立部署的CDN上, 所以 api server 要開啟CORS. 

      Program.cs 文件中增加兩個小節(jié)代碼:

      先為 builder 增加服務(wù): 

      builder.Services.AddCors(option =>
      {
          option.AddPolicy("CorsPolicy", policy => policy
          .AllowAnyOrigin()
          .AllowAnyHeader()
          .AllowAnyMethod());
      });

      其次, 需要在 web request 的pipleline 增加  UseCors() 中間件, pipeline 各個中間件順序至關(guān)重要, 

      可參考官網(wǎng): https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-5.0#middleware-order

      Cors 中間件緊跟在 UseRouting() 之后即可. 

      app.UseRouting();
      
      //Harry: enable Cors Policy, must be after Routing 
      app.UseCors("CorsPolicy");

       

      ===================================
      目標(biāo)3:  實(shí)現(xiàn)一個受保護(hù)的API
      ===================================

      增加一個獲取產(chǎn)品清單的API, 該API需要訪問方提供合法的JWT token才行. 

      步驟1: 增加nuget依賴包 Microsoft.AspNetCore.Authentication.JwtBearer

       

      步驟2: 增加 product 實(shí)體類

          public class Product
          {
              public int Id { get; set; }
              public string? Name { get; set; }
              public decimal Price { get; set; }
          }

       

      步驟3: 增加ProductsController 類

      using BlazorApp1.Shared;
      using Microsoft.AspNetCore.Authorization;
      using Microsoft.AspNetCore.Mvc;
      
      namespace BlazorApp1.Server.Controllers
      {
          [ApiController] 
          [Route("[controller]")]
          [Authorize]
          public class ProductsController : ControllerBase
          {
              [HttpGet]
              public IActionResult GetProducts()
              {
                  var products = new List<Product>()
                  {
                      new Product()
                      {
                          Id = 1,
                          Name = "Wireless mouse",
                          Price = 29.99m
                      },
                      new Product()
                      {
                          Id = 2,
                          Name = "HP printer",
                          Price = 100
                      },
                      new Product()
                      {
                          Id = 3,
                          Name = "Sony keyboard",
                          Price = 20
                      }
                  };
                  return Ok(products);
              }
      
          }
      
      }

       

      注意加上了 Authorize 注解后訪問url, 得到 500 報錯,  提示需要加上相應(yīng)的 Authorization 中間件. 

       

       

      app 增加 Authorization 中間件  app.UseAuthorization() 后, 測試包 401 錯誤, 說明授權(quán)這塊功能已經(jīng)OK. 

      測試效果圖: 

       [Authorize]  注解的說明:

      •  [Authorize] 不帶參數(shù): 只要通過身份驗證, 就能訪問
      •  [Authorize(Roles="Admin,User")],  只有 jwt token 的 Role Claim 包含 Admin 或 User 才能訪問, 這種方式被叫做基于role的授權(quán)
      • [Authorize(Policy="IsAdmin"] , 稱為基于Claim的授權(quán)機(jī)制. 它屬于基于Policy策略的授權(quán)的簡化版, 簡化版的Policy 授權(quán)檢查是看Jwt token中是否包含 IsAdmin claim,  如包含則授權(quán)驗證通過.
      •  [Authorize(Policy="UserOldThan20"],   基于Policy策略的授權(quán)機(jī)制, 它是基于 claim 授權(quán)的高級版, 不是簡單地看 token是否包含指定的 claim, 而是可以采用代碼邏輯來驗證, 實(shí)現(xiàn)較為復(fù)雜, 需要先實(shí)現(xiàn) IAuthorizationRequirement 和 IAuthorizationHander 接口. 
      • 基于資源的授權(quán), 這種機(jī)制更靈活,  參見 https://andrewlock.net/resource-specific-authorisation-in-asp-net-core/

       

      不管是基于Role還是基于Claim還是基于Policy的授權(quán)驗證,  token中都需要帶有特定claim, token內(nèi)的信息偏多, 帶來的問題是: 服務(wù)端簽發(fā)token較為復(fù)雜, 另外, token 中的一些信息很可能過期, 比如服務(wù)端已經(jīng)對某人的角色做了修改, 但客戶端token中的角色還是老樣子, 兩個地方的role不一致, 使得授權(quán)驗證更復(fù)雜了. 

      我個人推薦的做法是, API 僅僅加上不帶參數(shù)的  [Authorize] , 指明必須是登錄用戶才能訪問, 授權(quán)這塊完全控制在服務(wù)端, 從token中提取userId, 然后查詢用戶所在的 userGroup 是否具有該功能.  這里的 userGroup 和 role 完全是一回事.  accessString 和功能點(diǎn)是1:n的關(guān)系, 最好是能做到 1:1. 

       

       

       下面代碼是我推薦方案的偽代碼, 同時也展現(xiàn) Claim / Claims /ClaimsIdentity /ClaimsPrincipal 幾個類的關(guān)系:  

              [HttpGet]  
      [Authorize]
      public IActionResult get(int productId) { //構(gòu)建 Claims 清單 const string Issuer = "https://gov.uk"; var claims = new List<Claim> { new Claim(ClaimTypes.Name, "Andrew", ClaimValueTypes.String, Issuer), new Claim(ClaimTypes.Surname, "Lock", ClaimValueTypes.String, Issuer), new Claim(ClaimTypes.Country, "UK", ClaimValueTypes.String, Issuer), new Claim("ChildhoodHero", "Ronnie James Dio", ClaimValueTypes.String) }; //生成 ClaimsIdentity 對象 var userIdentity = new ClaimsIdentity(claims, "Passport"); //生成 ClaimsPrincipal 對象, 一般也叫做 userPrincipal var userPrincipal = new ClaimsPrincipal(userIdentity); object product = loadProductFromDb(productId); var hasRight = checkUserHasRight(userPrincipal, resource:product, acccessString: "Product.Get"); if (!hasRight) { return new UnauthorizedResult(); //返回401報錯 } else { return Ok(product); } } private bool checkUserHasRight(ClaimsPrincipal userPrincipal, object resource, string accessString) { throw new NotImplementedException(); // 自行實(shí)現(xiàn) } private object loadProductFromDb(int id) { throw new NotImplementedException(); // 自行實(shí)現(xiàn) }

       

       

      ===================================
      目標(biāo)4: 實(shí)現(xiàn)一個生成密碼hash的接口(測試用)
      ===================================

      這個小節(jié)主要是為登錄接口做數(shù)據(jù)準(zhǔn)備工作.  用戶的密碼不應(yīng)該是明文形式保存, 必須存儲加密后的密碼. 

      一般的 Password hash 算法, 需要我們自己指定 salt 值, 然后為我們生成一個哈希后的密碼摘要. 校驗密碼時候, 需要將最初的salt值和用戶傳入的原始密碼, 通過同樣的哈希算法, 得到另一個密碼摘要, 如果兩個密碼摘要一致, 表明新傳入的原始密碼是對的. 

      Asp.net core提供的默認(rèn) PasswordHasher 類, 提供了方便而且安全的密碼hash算法, 具體的討論見 https://stackoverflow.com/questions/20621950/  ,   PasswordHasher 類 Rfc2898算法, 不需要我們指定 salt 值, 有算法本身生成一個隨機(jī)的salt值,  并將該隨機(jī)的 salt 值存在最終的密碼hash中的前一部分, 所以驗證時也不需要提供該salt 值.

      該算法的特點(diǎn)是:

      • 使用非常簡單, 做hash之前不需要準(zhǔn)備 salt 值, 加密之后也不需要額外保存salt值, 
      • 同一個明文,多次做hash摘要會得到不同的結(jié)果. 

      下面是一個測試 controller 用于生成密碼hash值: 

      using Microsoft.AspNetCore.Identity;
      using Microsoft.AspNetCore.Mvc; 
      
      namespace BlazorApp1.Server.Controllers
      {
          [ApiController] 
          [Route("[controller]")]
      
          public class TestController : ControllerBase
          {
              private readonly IConfiguration _configuration;
              public TestController(IConfiguration configuration)=>_configuration = configuration;
      
              [HttpPost("GenerateHashedPwd")]
              public string Generate([FromBody] string plainPassword)
              { 
                  var passwordHasher=new PasswordHasher<String>();
                  var hashedPwd = passwordHasher.HashPassword("",plainPassword);
                  var verifyResult = passwordHasher.VerifyHashedPassword("", hashedPwd, plainPassword);
                  Console.WriteLine(verifyResult);
                  return hashedPwd;
              } 
       
          }
      
      }

       

      Rest client 指令:

      POST http://localhost:5223/Test/GenerateHashedPwd HTTP/1.1
      content-type: application/json
      
      "123abc"

      得到的hash值為: 

      AQAAAAEAACcQAAAAEGVtM0HmzqITBdnkZNzbdDwM3u7zz2F5XQfRIJN/78/UGM9u8Lqcn/eh4zWlUbbDmQ==

       

       ===================================
      目標(biāo)5:  實(shí)現(xiàn)登錄API
      =================================== 

      (1) appsettings.json 配置文件中, 新增 Credentials 清單,  代表我們的用戶庫. 

      使用上面的密碼hash接口, password 明文為  test-password, 對應(yīng)的密文為: 
      AQAAAAEAACcQAAAAENsLEigZGIs6kEdhJ7X1d7ChFZ4TKQHHYZCDoLSiPYy/GpYw4lmMOalsn8g/7debnA==
       
      為了簡單起見, 我們在 appsettings.json  僅新增一個 Credentials: 
        "Credentials": {
          "Email": "user@test.com",
          "Password": "AQAAAAEAACcQAAAAENsLEigZGIs6kEdhJ7X1d7ChFZ4TKQHHYZCDoLSiPYy/GpYw4lmMOalsn8g/7debnA=="
        }

       

      (2) appsettings.json 配置文件中, 增加 jwt 配置項, 用于jwt token的生成和驗證. 

      jwt token 的生成是由新的 LoginController 實(shí)現(xiàn), 

      jwt token的驗證是在 ASP.net Web的 Authentication 中間件完成的. 

        "Jwt": {
          "Key": "ITNN8mPfS2ivOqr1eRWK0Rac3sRAchQdG8BUy0pK4vQ3\",",
          "Issuer": "MyApp",
          "Audience": "MyAppAudience",
          "TokenExpiry": "60" //minutes
        }

       

       (3) 增加 Credentials 類, 用來傳入登錄的憑證信息. 

      public class Credentials
      { 
          [Required]
          public string Email { get; set; }
          [Required]
          public string Password { get; set; }
      }

      (4) 增加一個登錄結(jié)果類 LoginResult:

      public class LoginResult
      {  
          public string? Token { get; set; }
          public string? ErrorMessage { get; set; }
      }

       (5) 新增 LoginController API類 

      using BlazorApp1.Shared;
      using Microsoft.AspNetCore.Authorization;
      using Microsoft.AspNetCore.Identity;
      using Microsoft.AspNetCore.Mvc;
      using Microsoft.IdentityModel.Tokens;
      using System.IdentityModel.Tokens.Jwt;
      using System.Security.Claims;
      using System.Text;
      
      namespace BlazorApp1.Server.Controllers
      {
          [ApiController] 
          [Route("[controller]")]
      
          public class LoginController : ControllerBase
          {
              private readonly IConfiguration _configuration;
              public LoginController(IConfiguration configuration)=>_configuration = configuration;
      
              [HttpPost("login")]
              public LoginResult Login(Credentials credentials)
              {
                  var passed=ValidateCredentials(credentials);
                  if (passed)
                  {
                      return new LoginResult { Token = GenerateJwt(credentials.Email), ErrorMessage = "" };
                  }
                  else
                  {
                      return new LoginResult { Token = "", ErrorMessage = "Wrong password" };
                  }
              }
      
              bool ValidateCredentials(Credentials credentials)
              {
                  var user = _configuration.GetSection("Credentials").Get<Credentials>();
                  var password = user.Password;
                  var plainPassword = credentials.Password;
                  var passwordHasher =new PasswordHasher<string>();
                  var result= passwordHasher.VerifyHashedPassword(null, password, plainPassword);
                  return (result == PasswordVerificationResult.Success);
              }
      
              private string GenerateJwt(string email)
              {
                  var jwtKey = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]);
                  var securtiyKey = new SymmetricSecurityKey(jwtKey);
                  var issuer = _configuration["Jwt:Issuer"];
                  var audience=_configuration["Jwt:Audience"];
                  var tokenExpiry = Convert.ToDouble( _configuration["Jwt:TokenExpiry"]);
      
                  var token = new JwtSecurityToken(
                      issuer: issuer,
                      audience: audience,
                      expires: DateTime.Now.AddMinutes(tokenExpiry),
                      claims: new[] { new Claim(ClaimTypes.Name, email) },
                      signingCredentials: new SigningCredentials(securtiyKey, SecurityAlgorithms.HmacSha256)
                      );
                  var tokenHandler = new JwtSecurityTokenHandler(); 
      
                  return tokenHandler.WriteToken(token);
              }
       
          }
      
      }

       代碼說明: 

      • JwtSecurityToken 類的 claims 數(shù)組參數(shù),  對應(yīng)的是 JWT token payload key-value, 一個 claim 對應(yīng)一個key-value, 可以指定多個claim, 這樣 jwt token的 payload 會變長. 

                代碼中的 JwtSecurityToken 類的  claims 參數(shù), 其傳入值為 new[] { new Claim(ClaimTypes.Name, email) } , 說明 payload 僅有一個 claim 或者叫 key-value對,  其 key 為 name, value為郵箱號;  如果jwt token中要包含用戶的 Role, 可以再增加  new Claim(ClaimTypes.Role, "Admin")

      • JwtSecurityTokenHandler 類其實(shí)很關(guān)鍵, 可以將 Token 對象轉(zhuǎn)成字符串, 也可以用它驗證 token 字符串是否合法. 

       

       (5)  app 增加 Authentication 中間件

      using Microsoft.AspNetCore.Authentication.JwtBearer;
      using Microsoft.AspNetCore.ResponseCompression;
      using Microsoft.IdentityModel.Tokens;
      using System.Text;
      
      var builder = WebApplication.CreateBuilder(args);
      
      // Add services to the container.
      builder.Services.AddControllersWithViews();
      builder.Services.AddRazorPages();
      
      //Harry: Add Cors Policy service
      builder.Services.AddCors(option =>
      {
          option.AddPolicy("CorsPolicy", policy => policy
          .AllowAnyOrigin()
          .AllowAnyHeader()
          .AllowAnyMethod());
      });
      
      
      //Harry: Read Jwt settings
      var jwtIssuser = builder.Configuration["Jwt:Issuer"];
      var jwtAudience = builder.Configuration["Jwt:Audience"];
      var jwtKey = Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]);
      var securtiyKey = new SymmetricSecurityKey(jwtKey);
      
      //Harry: Add authentication service
      builder.Services.AddAuthentication("Bearer").AddJwtBearer(options => {
          options.TokenValidationParameters = new TokenValidationParameters
          {
              //驗證 Issuer
              ValidateIssuer = true,
              ValidIssuer = jwtIssuser,
      
              //驗證 Audience
              ValidateAudience = true,
              ValidAudience = jwtAudience,
      
              //驗證 Security key
              ValidateIssuerSigningKey = true,
              IssuerSigningKey = securtiyKey,
      
              //驗證有效性
              ValidateLifetime = true, 
              LifetimeValidator = (DateTime? notBefore, DateTime? expires, SecurityToken securityToken,
                                           TokenValidationParameters validationParameters) =>
              {
                  return expires<=DateTime.Now;
              }         
          };
      });
      
      
      var app = builder.Build();
      
      // Configure the HTTP request pipeline.
      if (app.Environment.IsDevelopment())
      {
          app.UseWebAssemblyDebugging();
      } 
      else
      {
          app.UseExceptionHandler("/Error");
      }
      
      app.UseBlazorFrameworkFiles();
      app.UseStaticFiles();
      
      app.UseRouting();
      
      //Harry: enable Cors Policy, must be after Routing 
      app.UseCors("CorsPolicy");
      
      //Harry: authentication and authorization middleware to pipeline. must be after Routing/Cors and before EndPoint configuation
      app.UseAuthentication();
      
      //Harry: add authorization middleware to pipeline. must be after Routing/Cors and before EndPoint configuation
      app.UseAuthorization();
      
      app.MapRazorPages();
      app.MapControllers();
      app.MapFallbackToFile("index.html");
      
      
      app.Run();

       

       Rest client測試代碼:

      GET http://localhost:5223/Products HTTP/1.1
      content-type: application/json
      Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidXNlckB0ZXN0LmNvbSIsImV4cCI6MTYzNTAwNTcyMywiaXNzIjoiTXlBcHAiLCJhdWQiOiJNeUFwcEF1ZGllbmNlIn0.6rGq0Ouay9-3bvTDWVEouCHg4T7tDv129PQTha4GhP8

      測試結(jié)果:

       

       

       

      ===================================

      參考
      ===================================

      https://www.mikesdotnetting.com/article/342/managing-authentication-token-expiry-in-webassembly-based-blazor
      https://chrissainty.com/avoiding-accesstokennotavailableexception-when-using-blazor-webassembly-hosted-template-with-individual-user-accounts/
      https://www.puresourcecode.com/dotnet/blazor/blazor-using-httpclient-with-authentication/
      https://code-maze.com/using-access-token-with-blazor-webassembly-httpclient/#accessing-protected-resources
      https://andrewlock.net/resource-specific-authorisation-in-asp-net-core/
      http://www.rzrgm.cn/wjsgzcn/p/12936257.html

      http://www.rzrgm.cn/ittranslator/p/making-http-requests-in-blazor-webassembly-apps.html

       

      posted @ 2021-10-24 13:30  harrychinese  閱讀(948)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国产av国片精品一区二区| 久久99九九精品久久久久蜜桃 | 亚洲精品乱码久久久久久蜜桃不卡| 民丰县| 色伦专区97中文字幕| 成人免费A级毛片无码片2022| 色综合久久中文综合久久激情| 一区二区亚洲精品国产精| 激情 自拍 另类 亚洲| 车致| 国产AV国片精品有毛| 国产精品无码专区| 四虎永久免费精品视频| 国产成人精品无码一区二区 | 天天做天天爱夜夜夜爽毛片| 一区二区在线观看成人午夜| 欧美xxxxx在线观看| 色一情一乱一区二区三区码| 草草浮力影院| caoporn成人免费公开| 亚洲精品成人一二三专区| 东京热大乱系列无码| 日韩伦理片| 亚洲熟妇无码另类久久久| 九九热在线视频观看精品| 亚洲理论在线A中文字幕| 少妇太爽了在线观看免费视频| 3d全彩无码啪啪本子全彩| 亚洲午夜香蕉久久精品| 国产一级黄色片在线播放| 成人啪精品视频网站午夜| 亚洲一区久久蜜臀av| 久久精品亚洲精品国产区| 欧洲性开放老太大| 男女啪啪高潮激烈免费版| 丘北县| 国产av国片精品一区二区| 色爱综合激情五月激情| 日本中文字幕久久网站| 激情综合网激情综合| 国产极品粉嫩馒头一线天|