【翻譯】配置基於角色的Blazor WebAssembly(Blazor客戶端)應用程式的授權

来源:https://www.cnblogs.com/chen8854/archive/2019/11/07/securing-your-blazor-apps-configuring-role-based-authorization-with-client-side-blazor.html
-Advertisement-
Play Games

什麼是基於角色的授權? 當涉及ASP.NET Core授權時,我們有兩種選擇,基於角色和基於策略(也有基於聲明的,但那隻是基於策略的一種特殊類型)。 基於角色的授權最初是在ASP.NET(ASP.NET Core之前)中引入,這是一種限制對資源訪問的聲明性方法。 開發人員可以指定用戶... ...


原文地址:https://chrissainty.com/securing-your-blazor-apps-configuring-role-based-authorization-with-client-side-blazor/

什麼是基於角色的授權?

 當涉及ASP.NET Core授權時,我們有兩種選擇,基於角色和基於策略(也有基於聲明的,但那隻是基於策略的一種特殊類型)。

基於角色的授權最初是在ASP.NET(ASP.NET Core之前)中引入,這是一種限制對資源訪問的聲明性方法。

開發人員可以指定用戶必須是其成員的特定角色的名稱,以便訪問特定的資源。一般是使用[Authorize]屬性指定一個角色或角色列表[Authorize(Roles="Admin")]。用戶可以是單個角色的成員,也可以是多個角色的成員。

如何創建和管理角色取決於所使用的備份存儲。到目前為止我們一直使用ASP.NET Core Identity,我們將繼續使用它來管理和存儲我們的角色。

本文章代碼將基於前一篇文章基礎上搭建。

設置ASP.NET Core Identity角色

我們需要添加角色服務到我們的應用中。我們需要更新Startup類中的ConfigureService方法。

            services.AddDefaultIdentity<IdentityUser>()
                .AddRoles<IdentityRole>()
                .AddEntityFrameworkStores<ApplicationDbContext>();

IdentityRole是ASP.NET Core Identity提供的預設角色類型。如果它無法滿足你的需求,你可以提供其他的角色類型。

接下來我們將為資料庫添加一些角色數據-添加一個用戶和管理員角色。為此,我們將重載ApplicationDbContext中的方法OnModelCreating

    public class ApplicationDbContext : IdentityDbContext
    {
        public ApplicationDbContext(DbContextOptions options) : base(options) {
        }

        protected override void OnModelCreating(ModelBuilder builder) {
            base.OnModelCreating(builder);

            builder.Entity<IdentityRole>().HasData(new IdentityRole { Name = "User", NormalizedName = "USER", Id = Guid.NewGuid().ToString(), ConcurrencyStamp = Guid.NewGuid().ToString() });
            builder.Entity<IdentityRole>().HasData(new IdentityRole { Name = "Admin", NormalizedName = "ADMIN", Id = Guid.NewGuid().ToString(), ConcurrencyStamp = Guid.NewGuid().ToString() });
        }
    }

完成之後,我們需要生成遷移,然後將其應用到資料庫。

    Add-Migration SeedRoles
    Update-Database

為角色分配用戶

現在我們已經有一些可用的角色了,我們現在來更新賬戶控制器(Accounts controller)創建用戶的動作。

在新增用戶時候為其分配User角色。如果新用戶的電子郵件以admin開頭,則為其分配UserAdmin角色組。

 

        [HttpPost]
        public async Task<IActionResult> Post([FromBody]RegisterModel model) {
            var newUser = new IdentityUser { UserName = model.Email, Email = model.Email };

            var result = await _userManager.CreateAsync(newUser, model.Password);

            if (!result.Succeeded) {
                var errors = result.Errors.Select(x => x.Description);

                return BadRequest(new RegisterResult { Successful = false, Errors = errors });

            }

            //為所有的新用戶分配User角色
            await _userManager.AddToRoleAsync(newUser, "User");

            //如果電子郵件以'admin'開頭則分配Admin角色
            if (newUser.Email.StartsWith("admin")) {
                await _userManager.AddToRoleAsync(newUser, "Admin");
            }

            return Ok(new RegisterResult { Successful = true });
        }

現在我們在用戶註冊時為其分配了角色,但我們需要將這些信息傳遞給Blazor。我們需要更新JSON Web Token中的聲明來處理這個需求。

將角色聲明添加到JWT

現在我們來更新登錄控制器(Login controller)中的Login方法。先以下用於生成聲明的代碼。

     var claims = new[]
     {
       new Claim(ClaimTypes.Name, login.Email)
     };

並使用以下代碼替換。

            var user = await _signInManager.UserManager.FindByEmailAsync(login.Email);
            var roles = await _signInManager.UserManager.GetRolesAsync(user);

            var claims= new List<Claim>();

            claims.Add(new Claim(ClaimTypes.Name, login.Email));

            foreach (var role in roles) {
                claims.Add(new Claim(ClaimTypes.Role, role));
            }

我們通過UserManager獲取當前用戶並獲取用戶擁有的角色。之前是將用戶電子郵件添加到Name聲明,現在如果用戶擁有角色,我們則迴圈將角色添加到Role聲明中。

關於角色聲明有一點比較很重要問題。如果一個用戶擁有兩個角色,那麼這兩個角色聲明會被添加到JWT中。

http://schemas.microsoft.com/ws/2008/06/identity/claims/role - "User"
http://schemas.microsoft.com/ws/2008/06/identity/claims/role - "Admin"

然後事實上並非如此,而是兩個角色合併為一個數組。

http://schemas.microsoft.com/ws/2008/06/identity/claims/role - ["User", "Admin"]

關於這一點很重要,在Blazor客戶端處理角色時需要註意。

在Blazor客戶端使用角色

我們將角色分配給新用戶,當他們登錄時,我們通過JWT返回這些角色。那麼在Blazor內部要如何使用角色呢?

在這個問題上目前微軟官方並未提供任何可以幫助我們處理角色的東西,所以我們必須手動處理它。

 

private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
        {
            var claims = new List<Claim>();
            var payload = jwt.Split('.')[1];
            var jsonBytes = ParseBase64WithoutPadding(payload);
            var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);

            keyValuePairs.TryGetValue(ClaimTypes.Role, out object roles);

            if (roles != null)
            {
                if (roles.ToString().Trim().StartsWith("["))
                {
                    var parsedRoles = JsonSerializer.Deserialize<string[]>(roles.ToString());

                    foreach (var parsedRole in parsedRoles)
                    {
                        claims.Add(new Claim(ClaimTypes.Role, parsedRole));
                    }
                }
                else
                {
                    claims.Add(new Claim(ClaimTypes.Role, roles.ToString()));
                }

                keyValuePairs.Remove(ClaimTypes.Role);
            }

            claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString())));

            return claims;
        }

        private byte[] ParseBase64WithoutPadding(string base64)
        {
            switch (base64.Length % 4)
            {
                case 2: base64 += "=="; break;
                case 3: base64 += "="; break;
            }
            return Convert.FromBase64String(base64);
        }

上面代碼對JWT進行解碼、提取聲明並返回聲明。但我們沒有涉及的是我對其進行了修改,以處理特殊情況下的角色。

如果存在角色聲明,那麼我們將檢查第一個字元是否為[,表名它是一個JSON數組。如果找到roles聲明,則解析提取角色名稱,迴圈遍歷角色名稱,並將每個角色名稱作為聲明添加。如果roles不是一個數組,則作為單個角色聲明添加。

這個方法不一定是最好的,但它確實實現了我們的目的。

我們需要更新MarkUserAsAuthenticated方法來調用ParseClaimsFromJwt

        public void MarkUserAsAuthenticated(string token) {
            var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt"));
            var authState = Task.FromResult(new AuthenticationState(authenticatedUser));

            NotifyAuthenticationStateChanged(authState);
        }

最後,我們需要更新AuthService中的Login方法,以便在調用MarkUserAsAuthenticated時傳遞令牌而不是電子郵件。

        public async Task<LoginResult> Login(LoginModel loginModel) {
            var result = await _httpClient.PostJsonAsync<LoginResult>("api/Login", loginModel);

            if (result.Successful) {
                await _localStorage.SetItemAsync("authToken", result.Token);
                ((ApiAuthenticationStateProvider)_authenticationStateProvider).MarkUserAsAuthenticated(result.Token);
                _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", result.Token);

                return result;
            }

            return result;
        }

現在,我們應該能夠將基於角色的授權應用到我們的應用程式中。我們來關註下API的處理。

將基於角色的授權應用於API

WeatherForecastController上的Get方法設置為僅對Admin角色中經過身份驗證的用戶可訪問。我們使用Authorize屬性並指定用於訪問它的角色。(這裡在預設生成模版與原文有出入)

        [HttpGet]
        [Authorize(Roles = "Admin")]
        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();
        }

如果您創建一個屬於Admin角色的新用戶,併在Blazor應用程式中訪問Fetch Data頁面,您應該可以看到一切都按預期的載入。

但你如果創建一個普通的用戶並執行相同的操作,您應該會看到頁面被卡在Loading...

 

在Blazor中使用基於角色的授權

Blazor還可以使用Authorize屬性來保護頁面。這是通過使用@attribute指令來應該[Authorize]屬性來實現的。您還可以使用AuthorizeView組件來限制對頁面部分的訪問。

在 Blazor WebAssembly 應用中,可以繞過授權檢查,因為用戶可以修改所有客戶端代碼。 所有客戶端應用程式技術都是如此,其中包括 JavaScript SPA 框架或任何操作系統的本機應用程式。

始終對客戶端應用程式訪問的任何 API 終結點內的伺服器執行授權檢查。

由於預測數據只對管理員用戶可用,所以我們使用Authorize屬性限制對該頁面的訪問。

@page "/fetchdata"
@attribute [Authorize(Roles = "Admin")]

現在嘗試使用管理用戶登錄到該頁面。一切應該都正常載入。然後嘗試使用普通用戶登錄,您應該會看到一條未經授權的消息。

我們來測試一下AuthorizeView,在主頁(index.razor)添加如下代碼。

<AuthorizeView Roles="User">
    <p>You can only see this if you're in the User role.</p>
</AuthorizeView>

<AuthorizeView Roles="Admin">
    <p>You can only see this if you're in the Admin role.</p>
</AuthorizeView>

同樣,使用管理員用戶賬戶登錄。您應該看到這兩條消息,因為您同時擁有這兩個角色許可權。

如果您使用普通用戶登錄則只能看到第一條消息。

總結

在這篇文章中,我們瞭解了什麼是基於角色的授權以及如何使用ASP.NET Core Identity來設置和管理角色。然後我們討論瞭如何使用JSON Web Tokens將角色從API傳遞給客戶端並處理在Blazor中的角色聲明,最後在API和Blazor上實現一些基於角色的授權檢查。

我只是想重申一下,您不能僅僅依賴於客戶端身份驗證或授權,客戶端永遠不能被信任。必須始終在伺服器上執行身份驗證和授權檢查。

 

附上代碼(Github)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 以前寫過ASP.NET Core 2.x的REST API文章,今年再更新一下到3.0版本。 先決條件 我在B站有一個非常入門的ASP.NET Core 3.0的視頻教程,如果您對ASP.NET Core不瞭解,就可以先看一下裡面的基礎知識和API相關的內容,地址是:https://www.bili ...
  • 如何在IIS中設置功能變數名稱: 1,想好我們想要配置的本地功能變數名稱,我們以www.baidu.com為例。 2,打開系統盤,一般預設的系統盤為C盤,打開:C:\Windows\System32\drivers\etc這路徑,找到文件“hosts”文件。 3,打開文件hosts文件,在最下方回車加入:電腦ip地 ...
  • 多年來,Javascript(及其子框架)已在瀏覽器中運行DOM(文檔對象模型),並且掌握了腳本知識才能真正操作客戶端UI。大約2年前,所有這些都隨著Web Assembly的引入而發生了變化-Web Assembly允許在客戶端解釋已編譯的語言(相對Web Assembly更多瞭解請閱讀瞭解was ...
  • 一處開發,多處同步編輯使用,並且發佈時各個項目均可獨立 一、直接編輯項目工程文件 具體實現為:編輯 文件,在 或 節點: : 屬性值為項目文件的相對引用路徑 : 節點中放置要引用到當前項目中的位置 1.無需編譯的靜態資源文件等,使用 標簽引入 引用當前工程內的文件 引用外部項目工程中的文件 2.需要 ...
  • 場景 通過文件選擇對話框選擇文件 複製文件到指定路徑 註: 博客主頁: https://blog.csdn.net/badao_liumang_qizhi 關註公眾號霸道的程式猿獲取編程相關電子書、教程推送與免費下載。 實現 打開選擇文件對話框 OpenFileDialog importOpenFi ...
  • [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Winapi)] internal static extern IntPtr GetFocus(); ///獲取 當前擁有焦點 ...
  • 場景 C#中File類的常用讀取與寫入文件方法的使用: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/99693983 註: 博客主頁:https://blog.csdn.net/badao_liumang_qizhi關註公眾號霸 ...
  • 一個基於Net Core3.0的WPF框架Hello World實例 [toc] 1.創建WPF解決方案 1.1 創建Net Core版本的WPF工程 1.2 指定項目名稱,路徑,解決方案名稱 2. 依賴庫和4個程式文件介紹 2.1 框架依賴庫 依賴Microsoft.NETCore.App跟Mic ...
一周排行
    -Advertisement-
    Play Games
  • JWT(JSON Web Token)是一種用於在網路應用之間傳遞信息的開放標準(RFC 7519)。它使用 JSON 對象在安全可靠的方式下傳遞信息,通常用於身份驗證和信息交換。 在Web API中,JWT通常用於對用戶進行身份驗證和授權。當用戶登錄成功後,伺服器會生成一個Token並返回給客戶端 ...
  • 老周在幾個世紀前曾寫過樹莓派相關的 iOT 水文,之所以沒寫 Nano Framework 相關的內容,是因為那時候這貨還不成熟,可玩性不高。不過,這貨現在已經相對完善,老周都把它用在項目上了——第一個是自製的智能插座,這個某寶上50多塊可以買到,搜“esp32 插座”就能找到。一種是 86 型盒子 ...
  • 引言 上一篇我們創建了一個Sample.Api項目和Sample.Repository,並且帶大家熟悉了一下Moq的概念,這一章我們來實戰一下在xUnit項目使用依賴註入。 Xunit.DependencyInjection Xunit.DependencyInjection 是一個用於 xUnit ...
  • 在 Avalonia 中,樣式是定義控制項外觀的一種方式,而控制項主題則是一組樣式和資源,用於定義應用程式的整體外觀和感覺。本文將深入探討這些概念,並提供示例代碼以幫助您更好地理解它們。 樣式是什麼? 樣式是一組屬性,用於定義控制項的外觀。它們可以包括背景色、邊框、字體樣式等。在 Avalonia 中,樣 ...
  • 在處理大型Excel工作簿時,有時候我們需要在工作表中凍結窗格,這樣可以在滾動查看數據的同時保持某些行或列固定不動。凍結窗格可以幫助我們更容易地導航和理解複雜的數據集。相反,當你不需要凍結窗格時,你可能需要解凍它們以獲得完整的視野。 下麵將介紹如何使用免費.NET庫通過C#實現凍結Excel視窗以鎖 ...
  • .NET 部署 IIS 的簡單步驟一: 下載 dotnet-hosting-x.y.z-win.exe ,下載地址:.NET Downloads (Linux, macOS, and Windows) (microsoft.com) .NET 部署 IIS 的簡單步驟二: 選擇對應的版本,點擊進入詳 ...
  • 拓展閱讀 資料庫設計工具-08-概覽 資料庫設計工具-08-powerdesigner 資料庫設計工具-09-mysql workbench 資料庫設計工具-10-dbdesign 資料庫設計工具-11-dbeaver 資料庫設計工具-12-pgmodeler 資料庫設計工具-13-erdplus ...
  • 初識STL STL,(Standard Template Library),即"標準模板庫",由惠普實驗室開發,STL中提供了非常多對信息學奧賽很有用的東西。 vector vetor是STL中的一個容器,可以看作一個不定長的數組,其基本形式為: vector<數據類型> 名字; 如: vector ...
  • 前言 最近自己做了個 Falsk 小項目,在部署上伺服器的時候,發現雖然不乏相關教程,但大多都是將自己項目代碼複製出來,不講核心邏輯,不太簡潔,於是將自己部署的經驗寫成內容分享出來。 uWSGI 簡介 uWSGI: 一種實現了多種協議(包括 uwsgi、http)並能提供伺服器搭建功能的 Pytho ...
  • 1 文本Embedding 將整個文本轉化為實數向量的技術。 Embedding優點是可將離散的詞語或句子轉化為連續的向量,就可用數學方法來處理詞語或句子,捕捉到文本的語義信息,文本和文本的關係信息。 ◉ 優質的Embedding通常會讓語義相似的文本在空間中彼此接近 ◉ 優質的Embedding相 ...