日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[Mvp.Blazor] 集成Ids4,实现统一授权认证

發布時間:2023/12/4 编程问答 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [Mvp.Blazor] 集成Ids4,实现统一授权认证 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

(又一個客戶端集成了IdentityServer4)

還是很開心的,目前已經有六個開源項目都集成到了Ids4認證中心了。

1、Blazor系列文章回顧

書接上文,關于Blazor學習呢,我也發了幾篇文章了,我一般寫東西都喜歡偏實戰,當然也有系列教程的情節,還記得當時在群里,我說簡單看看,淺嘗輒止吧,沒想到慢慢的發現了解的就越來越深入了,這里我我們再來一個前情回顧:

《我的『MVP.Blazor』快速創建與部署》

在這篇文章中,我們簡單的了解了下,什么的Blazor,他能做些什么,以及如何快速的入門和部署,屬于一個認知的階段,熟話說萬事開頭難,只要有興趣了,剩下的就是勤為徑;

《最終選型 Blazor.Server:又快又穩!》

從這篇文章開始,慢慢的開始實戰了,因為剛開始選型的是blazor.wasm,后來發現速度上比較慢,特別是刷新上,所以就最終選型了Blazor.Server了,速度當然沒得說,和我們平時的ASP.NETCore是一樣的,不過很多人說對硬件要求高,我感覺沒什么感覺,2C4G的Linux服務器,幾千人在線應該沒問題。然后就正式開始了設計我的MVP項目;

《[號外] Blazor wasm 其實也挺快!》

選型了server版本以后,總感覺wasm版本不可能那么慢,然后就好好的深入研究了下,通過了PWA、GZIP壓縮、CDN等技術,基本能保證WASM框架首屏首次刷新在3~5s之內,之后靜態加載毫秒級別,動態刷新是2s以內(可以查看我文章,有具體的數據佐證);

《[Mvp.Blazor] 動態路由與鉤子函數》

之前三篇文章,我們學會了組件通信、數據請求、數據綁定和繼承等知識點,那這篇文章我簡單的對路由和鉤子函數做了說明和講解,已經算是比較完善的項目了;

《如何給Blazor.Server加個API鑒權?》

我經常在群里說的一句話就是:沒有日志的項目是沒有靈魂的,沒有權限的項目是裸奔的。就是這樣的,所以我基本任何項目都會有權限,包括我們功能內部的一些小Portal,我都會在重要頁面或數據上增加一定的權限。這篇文章我用了很簡單,可以說很low的方法,對資源api實現了鑒權,當然,我在文章中也說了,這種方案肯定不靠譜。

所以,在這個端午節三天期間內,趁著沒地方去,我又各種的翻看資料,這里說下,國外的資料還是比較豐富的,有條件的話,還是要科學上網更好些。

最終呢,不負眾望,實現了將Blazor.Server集成到了Ids4的統一認證平臺上,如果你用的是Blazor.wasm,基本差不多,甚至更簡單,等你有實戰項目了就知道了。


這里先說明一下,因為畢竟是集成Ids4,涉及的知識會比較多,比如如何使用oidc-client、如何c#調用js事件、如何封裝service模塊,不過本文就不過多的對這幾個知識點講解原理了,先列出來操作步驟和代碼,畢竟篇幅有限,之后我會針對我認為比較重要的知識點稍微講解講解。

2、Ids4模塊配置

如果你之前開發過Ids4呢,接下來已經能看懂,如果完全不會,建議還是先把Ids4學一遍吧,除非就完全copy我的代碼,盡管會遇到這樣那樣的Bug。

涉及到的頁面和模塊

(藍色背景的三個文件)

1、先在認證中心配置Client

我們既然要集成認證平臺,那肯定要去認證中心,配置一個客戶端,因為我們的Blazor是一個前端的框架,所以我們使用implicit簡化模式,和Blog.Admin很相似,只不過一個組件安裝一個是直接使用js靜態文件,原理都一樣。

(Blazor客戶端的基本配置)


詳細應該能看的懂,注意一點就是需要配置

AllowAccessTokensViaBrowser = true

這樣才能有資格接收認證平臺返回過來的access_token。

2、客戶端配置config.js

首先需要下載或者從admin項目中拷貝出來oidc-client.js文件:

然后就是設計配置文件,我取名為app.js,主要還是連接ids4的相關內容:

?var?url?=?window.location.origin;var settings = {authority: "https://ids.neters.club",client_id: "blazorjs",redirect_uri: url + '/callback',post_logout_redirect_uri: url,response_type: 'id_token token',scope: 'openid profile roles blog.core.api',popup_redirect_uri: url + '/callback',popup_post_logout_redirect_uri: url,silent_redirect_uri: url + '/silent',automaticSilentRenew: false,filterProtocolClaims: true,loadUserInfo: true,revokeAccessTokenOnSignout: true};var mgr = new Oidc.UserManager(settings);///// events///mgr.events.addAccessTokenExpiring(function () {console.log("token?expiring");// maybe do this code manually if automaticSilentRenew doesn't work for youmgr.signinSilent().then(function (user) {console.log("silent renew success", user);}).catch(function (e) {console.error("silent renew error", e.message);})});mgr.events.addAccessTokenExpired(function () {console.log("token expired");});mgr.events.addSilentRenewError(function (e) {console.log("silent renew error", e.message);});mgr.events.addUserLoaded(function (user) {console.log("user loaded", user);mgr.getUser().then(function () {console.log("getUser loaded user after userLoaded event fired");});});mgr.events.addUserUnloaded(function (e) {console.log("user unloaded");});

這里先看看熱鬧即可,具體的代碼我建議還是直接從我的項目中獲取,具體內容不做贅述;

3、blazor項目引用

我們都知道Blazor.Server更像是一個netcore項目,那如何引用js文件呢,很簡單,之前的文章中我也講過,有一個統一的主頁面,用來承載整個app,那就是_Host.cshtml,我們就這幾在這里引用即可,如果你是用WASM的話,直接有一個index.html,和這個是同一個道理:

(在Blazor.Server中引用js文件)

那現在我們都配置好了客戶端和連接,也引用到了Blazor項目里,那如何去調用具體的js方法呢,請往下繼續看。

3、C#調用js方法模塊

是不是如果你看到這個邏輯都很怪異,我們都知道c#和js完全就不是一個邏輯,那是如何相互調用的呢,不僅c#可以使用js方法,我們也同樣能在js里去調用c#代碼,當然這是在Blazor框架里,你用mvc還是比較復雜的,平時我們也是習慣用signalR來實現的雙工通信。

這一模塊對應的代碼(藍色背景部分):

那我以登錄為例子,講解如何C#調用js吧:

1、注入JS運行時

我們如果想調用js,肯定需要一個運行時環境,這里已經給我們提供給了一個封裝好的接口,直接注入即可:

@inject IJSRuntime JS

然后在@code代碼塊中,我們使用JS,可以看到有兩個異步方法:

2、封裝擴展方法

這個就是用來幫助我們去Invoke腳本方法的,原理不解釋,直接封裝擴展:

/// <summary>/// JSRuntime擴展類/// 用來調取app.js文件/// </summary>public static class JSRuntimeExtensions{public async static Task SignInAsync(this IJSRuntime jsRuntime){await jsRuntime.InvokeAsync<object>("users.startSigninMainWindow");}}

括號中的參數呢,是調用的js方法名稱,user.xxxx,注意這個格式,下文會將如何寫這個js方法,而且,也可以傳遞參數,像這樣:

public async static Task SetUserInfoToStorage(this IJSRuntime jsRuntime, UserInfoModel userInfoModel) {await jsRuntime.InvokeAsync<UserInfoModel>("users.setUserInfoToStorage", userInfoModel); }

當然也可以用返回值,不過這里有一個小坑,js時間轉成c#時間的時候,會少八個小時,自己注意一下就行:

public async static Task<UserInfoModel> GetUserInfoFromStorage(this IJSRuntime jsRuntime){return await jsRuntime.InvokeAsync<UserInfoModel>("users.getUserInfoFromStorage");}

具體的還是看我的源碼吧,否則文章會比較長。

3、然后,C#調用擴展

其實也不一定需要封裝擴展,直接用原生的invoke也是一樣的,不過現在我通過開源了Blog.Core項目以后,越來越多封裝情有獨鐘了。

@code {protected override async Task OnAfterRenderAsync(bool firstRender){await JS.SignInAsync();} }

是不是很簡單,這樣就直接可以在c#中,調用js腳本方法了,但是這個js方法任意寫function就行了么,并不是。

4、最后,封裝js方法

還是用上邊的例子:users.startSigninMainWindow 這個方法,對應的js是這樣的:

window.users = {startSigninMainWindow: function () {// ...},}

里邊的內容很簡單,就是調用上一節的oidc-client的方法,主要是外邊的結構,自己把握一下就明白了,對應在瀏覽器中是這樣的,相當于給window窗體增加一個屬性:

這個我用著還挺好上手的,如果很多小伙伴不懂的話,以后在單獨寫文章吧。

到了這里,我們已經配置了ids4模塊、c#調用模塊,那就剩下最后一個模塊:調用資源服務器的service服務模塊了

4、調用service模塊

不知道大家還記得不記得,在之前的簡單的鑒權中,我是通過一個input輸入框,手動輸入token的方案,還是很low的:

那現在我們就不需要手動配置了,用了ids4后,一切都是自動的,所以還需要繼續做封裝。

這一部分涉及的代碼:

1、獲取訪問狀態——token

在上一節中,我們說到了用c#來調用js,在用戶登錄成功后,獲取用戶信息,然后保存到了localstorage里,現在我們如果要發送http請求,就肯定每次獲取access_token然后添加到htpp報頭里。

public async Task<string> GetAccessToken(){var userInfo = await _jS.GetUserInfoFromStorage();if (!IsLogin(userInfo)){await _jS.SignInAsync();}return userInfo.AccessToken;}public bool IsLogin(UserInfoModel UserInfo) =>UserInfo != null && UserInfo.AccessToken.IsNotEmptyOrNull() && !UserInfo.IsExpired();

我們這里做了封裝,等token失效的時候,會重新去ids4認證中心拉取新的令牌。

2、封裝Http操作

上邊我們已經獲取到了token,接下來就需要發送了,使用的是HttpClient,那每次都設置肯定比較麻煩,感覺再來個封裝:

public abstract class BaseService{protected BaseService(IServiceProvider serviceProvider){ServiceProvider = serviceProvider;}protected IServiceProvider ServiceProvider { get; }protected HttpClient HttpClient => ServiceProvider.GetService<HttpClient>();protected IJSRuntime JS => ServiceProvider.GetService<IJSRuntime>();protected AccessState AccessState => ServiceProvider.GetService<AccessState>();protected async Task<HttpClient> SecurityHttpClientAsync(){var httpClient = ServiceProvider.GetService<HttpClient>();httpClient.DefaultRequestHeaders.Remove("Authorization");var token = await AccessState.GetAccessToken();httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");httpClient.BaseAddress = new Uri("http://apk.neters.club");return httpClient;}}

這是一個抽象基類,然后每個服務繼承了就行了。

PS:這里的資源服務用的Blog.Core的接口,你可以用自己的各種服務,無論是webservice,restful還是grpc。

3、定義Blog具體服務

有了服務基類以后,我們在定義每一個基礎服務的時候,就簡單了不少,只關注業務邏輯即可,不用關心令牌權限了:

/// <summary>/// 服務基類/// 主要用來對Http請求的基礎封裝/// </summary>public class BlogService : BaseService{/// <summary>/// 構造函數/// </summary>/// <param name="serviceProvider"></param>public BlogService(IServiceProvider serviceProvider) : base(serviceProvider){}/// <summary>/// 獲取全部博文/// </summary>/// <param name="types"></param>/// <param name="page"></param>/// <returns></returns>public async Task<MessageModel<List<BlogArticle>>> GetBlogs(string types, int page = 1){var httpClient = await SecurityHttpClientAsync();return await httpClient.GetFromJsonAsync<MessageModel<List<BlogArticle>>>($"/api/Blog/GetBlogsByTypesForMVP?types={types}&page={page}");}}

是不是就是很普通的調用接口了!

4、前端調用

前端就很簡單了,注入我們的blogservice,然后發送請求即可:

@inject?BlogService?BlogService@using Blog.MVP.Blazor.SSR.Pages.Post.component<h1>編輯</h1><Editor BlogArticle="BlogArticle" OnSaveCallback="OnSaveAsync"></Editor><div class="text-danger">@_errmsg </div>@code {[Parameter]public int Id { get; set; }private BlogArticle BlogArticle { get; set; }private string _errmsg = "";protected override async Task OnInitializedAsync(){BlogArticle = (await BlogService.GetBlogByIdForMVP(Id)).response;}private async Task OnSaveAsync(BlogArticle blogArticle){BlogArticle?=?blogArticle;var?result?=?await?BlogService.UpdateBlog(BlogArticle);if (result.IsSuccessStatusCode){NavManager.NavigateTo("/blog/list");}else{_errmsg = "保存失敗! 錯誤原因:" + result.ReasonPhrase + "。請重試或登錄";}} }

最后別忘了startup注冊服務

// services and stateservices.AddScoped<BlogService>();services.AddScoped<AccessState>();

5、總結

經過上邊幾步的操作,我們已經可以發送請求了,來先看看效果:

(這里有一個小瑕疵,登錄后右上角個人信息需要刷新,以后再優化)

已經實現了單點登錄、注銷,授權驗證等等功能,如果沒有權限,就提示無權限:

重要說明

雖然我們已經寫完了,也很流暢,但是這里有一個問題:

如果想要在頁面進入的時候初始化就調用js事件。

比如:如果你想在進入一個頁面的時候,就需要權限需要去登錄,就比如我的blog/list頁面,我在獲取service的時候,會先判斷access_token,如果不存在就去登錄,那這個時候肯定需要調用js事件。

你可能會這么寫:

????protected?override?async?Task?OnInitializedAsync(){BlogArticle = (await BlogService.GetBlogByIdForMVP(Id)).response;}

但是只要去調用或者去刷新,可能會遇到這個一個問題:

它的意思是,我們不能在初始化的時候對頁面進行js操作,必須要頁面渲染完成才可以,

那這個時候就要考慮那三個階段六個鉤子了,官方已經提醒我們使用OnAfterRenderAsync了,但是又有一個問題是,如果你這么寫,頁面的data就無法渲染,已經我們這是在頁面加載完成了才會獲取的service。

經過我搜索stack overflow,發現已經有人趟過了:

https://stackoverflow.com/questions/61438796/javascript-interop-error-when-calling-javascript-from-oninitializedasync-blazor

可見生命周期還是要好好學學的。

好啦,假期也結束了,該收收心了,記得我的DDD領域驅動設計概論視頻也發布了,記得去看看,有問題盡量視頻下邊留言,群里討論太亂了。

拜拜。

總結

以上是生活随笔為你收集整理的[Mvp.Blazor] 集成Ids4,实现统一授权认证的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。