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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Blazor.Server以正确的方式集成Ids4

發(fā)布時(shí)間:2023/12/4 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Blazor.Server以正确的方式集成Ids4 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

(一個(gè)真正的以后端形式來集成認(rèn)證中心的方案)

?

本文導(dǎo)讀

首先特別感謝張善友老師提供技術(shù)指導(dǎo),源于上周我發(fā)了一篇文章

《[Mvp.Blazor] 集成Ids4,實(shí)現(xiàn)統(tǒng)一授權(quán)認(rèn)證》,

我本來是想通過像vue框架那樣,通過引oidc-client.js的方式,來實(shí)現(xiàn)Ids4的集成問題,我當(dāng)時(shí)以為已經(jīng)很好的,后來看了張隊(duì)發(fā)的文章以后,發(fā)現(xiàn)好像我寫的那種方式并不優(yōu)雅。

所以我又重新改了一次,(但是代碼保留了,新建了對應(yīng)的分支),以適應(yīng)在Blazor服務(wù)端集成ids4的完美體驗(yàn),如果你是wasm的項(xiàng)目,也不需要引用,張隊(duì)已經(jīng)寫好了組件,大家看看引用下即可:

https://github.com/BlazorHub/AntDesignTemplate

那今天我就快速的給大家說一下,如何在Blazor服務(wù)端來設(shè)計(jì)和集成認(rèn)證中心,當(dāng)然里邊會涉及一些基礎(chǔ)知識點(diǎn),我就不展開了,所以你自己需要先掌握以下知識儲備:

Ids4配置授權(quán)碼模式客戶端

Razor page的On{handler}{Async}()語法

HttpContext.User基本使用

第一部分:配置認(rèn)證方案

在上一篇文章中,我們主要是通過oidc-client.js的形式進(jìn)行ids4的連接的。

但是我們的項(xiàng)目畢竟是服務(wù)端,Blazor服務(wù)端使用ids4,感覺和MVC還是有些相似的,都是基于Cookie的oidc認(rèn)證模式。

認(rèn)證中心配置下客戶

你可以看到,基本就是和MVC配置是一樣的,不僅認(rèn)證中心的客戶端配置很像,就連項(xiàng)目中,認(rèn)證服務(wù)的注冊的方式也是幾乎一樣:

引用nuget包

Microsoft.AspNetCore.Authentication.OpenIdConnect

startup中,注冊認(rèn)證服務(wù)

// 第一步:配置認(rèn)證方案services.AddAuthentication(options =>{options.DefaultScheme = "Cookies";options.DefaultChallengeScheme = "oidc";}).AddCookie("Cookies").AddOpenIdConnect("oidc", options =>{options.Authority = "https://ids.neters.club/";options.ClientId = "blazorserver"; // 75 secondsoptions.ClientSecret = "secret";options.ResponseType = "code";options.SaveTokens = true;// 為api在使用refresh_token的時(shí)候,配置offline_access作用域options.GetClaimsFromUserInfoEndpoint = true;// 作用域獲取options.Scope.Clear();options.Scope.Add("roles");//"roles"options.Scope.Add("rolename");//"rolename"options.Scope.Add("blog.core.api");options.Scope.Add("profile");options.Scope.Add("openid");options.Events = new OpenIdConnectEvents{// called if user clicks Cancel during loginOnAccessDenied = context =>{context.HandleResponse();context.Response.Redirect("/");return Task.CompletedTask;}};});

相應(yīng)的注釋,我簡單的寫了寫,當(dāng)然文章的開篇我也說了,這一塊屬于ids4的基礎(chǔ)部分,以前的文章和視頻說了很多了,以后我就不打算講解了。

重點(diǎn)是要配置那幾個(gè)Scope作用域,然后可以看到有ids4的授權(quán)頁面,當(dāng)然,這個(gè)頁面也可以屏蔽掉不顯示。

注冊好了服務(wù),那肯定是要開啟中間件了:

開啟中間件

app.UseAuthentication();

第二部分:登錄、登出的頁面設(shè)計(jì)

這里我們使用到了Razor的Page功能,添加登錄和登出功能,具體的使用方法可以在微軟官網(wǎng)查看,相應(yīng)的代碼很簡單:

登錄、登出

//?這里用到了緩存來管理我們的用戶登錄信息,下文會講到// 第二部分: 配置razor page,定義登錄,登出等邏輯public class _HostAuthModel : PageModel{public readonly AuthStateCache Cache;public _HostAuthModel(AuthStateCache cache){Cache = cache;}// 每次刷新頁面異步加載public async Task<IActionResult> OnGet(){System.Diagnostics.Debug.WriteLine($"\n_Host OnGet IsAuth? {User.Identity.IsAuthenticated}");// 判斷Httpcontext是否登錄狀態(tài)if (User.Identity.IsAuthenticated){var sid = User.Claims.Where(c => c.Type.Equals("sid")).Select(c => c.Value).FirstOrDefault();System.Diagnostics.Debug.WriteLine($"sid: {sid}");// 如果緩存中不存在if (sid != null && !Cache.HasSubjectId(sid)){var authResult = await HttpContext.AuthenticateAsync("oidc");DateTimeOffset expiration = authResult.Properties.ExpiresUtc.Value;string accessToken = await HttpContext.GetTokenAsync("access_token");string refreshToken = await HttpContext.GetTokenAsync("refresh_token");Cache.Add(sid, expiration, accessToken, refreshToken);}}return Page();}// 登錄public IActionResult OnGetLogin(){System.Diagnostics.Debug.WriteLine("\n_Host OnGetLogin");var authProps = new AuthenticationProperties{IsPersistent = true,// 設(shè)置token的過期時(shí)間,相當(dāng)于前端的localstorageExpiresUtc = DateTimeOffset.UtcNow.AddHours(1),RedirectUri = Url.Content("~/")};// 認(rèn)證中心登錄return Challenge(authProps, "oidc");}// 登出public async Task OnGetLogout(){System.Diagnostics.Debug.WriteLine("\n_Host OnGetLogout");var authProps = new AuthenticationProperties{RedirectUri = Url.Content("~/")};await HttpContext.SignOutAsync("Cookies");await HttpContext.SignOutAsync("oidc", authProps);}}

代碼中,我已經(jīng)增加了相應(yīng)的注釋信息,你應(yīng)該能看的明白。

只不過具體的寫法有些小伙伴可能沒用過RazorPage,這里簡單的說一下:

因?yàn)槲覀兊腎ndex頁面沒有綁定任何數(shù)據(jù),所以這里基本上只繼承了PageModel,OnGet方法是個(gè)約定,查看mvc的源碼你會發(fā)現(xiàn)它會獲取On{handler}{Async}()。比如OnGet,它會在Get Index的時(shí)候被執(zhí)行,我們可以通過這個(gè)約定進(jìn)行數(shù)據(jù)綁定,這里知道下在Razor Page下HttpMethod也是一個(gè)handler,所以Razor Page的處理方式是通過handler進(jìn)行的。

為了實(shí)現(xiàn)這個(gè)效果,我們還需要配置主頁面_Host.cshtml的路由:

@page "/{handler?}"

你可能會好奇,那既然要使用到認(rèn)證中心了,為啥還需要登錄登出呢,其實(shí)客戶端都是需要的,不信你用mvc項(xiàng)目,也需要配置的。

權(quán)限組件

Blazor自帶了相應(yīng)的授權(quán)組件,可以很好的幫助我們來實(shí)現(xiàn)對權(quán)限的控制,只需要在App.razor中:

@inject NavigationManager NavManager<Router AppAssembly="@typeof(Program).Assembly"><Found Context="routeData"><AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"><NotAuthorized>@{// 使用權(quán)限組件,如果當(dāng)然組件配置Authorize,并且用戶未登錄,則跳轉(zhuǎn)登錄頁(這里是ids4)NavManager.NavigateTo("/Login", true);}</NotAuthorized><Authorizing><h1>Authentication in progress</h1><p>Only visible while authentication is in progress.</p></Authorizing></AuthorizeRouteView></Found><NotFound><CascadingAuthenticationState><LayoutView Layout="@typeof(MainLayout)"><h1>Sorry</h1><p>Sorry, there's nothing at this address.</p></LayoutView></CascadingAuthenticationState></NotFound> </Router>

大概意思就是,我們可以指定我們的razor頁面是否需要加權(quán),如果不配置,那就是很正常的瀏覽,比如我們的博客index首頁,肯定不能加權(quán),除非是后臺管理系統(tǒng),那就需要每個(gè)頁面都加權(quán)了,配置好后,如果用戶未登錄,那就會立刻跳轉(zhuǎn)到上邊我們配置的登錄地址,跳轉(zhuǎn)到認(rèn)證中心。

那如何對特定頁面加權(quán)呢,很簡單。

razor頁面加權(quán)

只需要在需要的頁面內(nèi)增加特性即可:

@attribute [Authorize]

展示用戶狀態(tài)

剛剛上邊我們已經(jīng)配置好了用戶登錄和登出接口,也對頁面進(jìn)行了加權(quán),用來引導(dǎo)用戶去認(rèn)證中心登錄,或者單點(diǎn)登錄,拉取用戶信息,那如何展示呢?

很簡單,在主頁面_Host.cshtml中,使用User屬性來實(shí)現(xiàn):

@model _HostAuthModel@if (User.Identity.IsAuthenticated){<div id="logined" style="display: contents;"><div class="menu-item my-2 my-md-0 mr-md-3 dropdown"><button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">設(shè)置 - <span id="username">@(userName) </span></button><div class="dropdown-menu"></div></div><a class="menu-item my-2 btn btn-outline-primary" href="/logout">注銷</a></div>}else{<div id="accessed"><a class="menu-item my-2 btn btn-outline-primary" href="/login">登入</a></div>}

具體的代碼看我的項(xiàng)目即可。

那到了這里,我們已經(jīng)完成了Blazor服務(wù)端如何集成ids4的代碼,不過這樣還是有些問題的,比如:

如果獲取access_token來訪問第三方的資源服務(wù)器api呢?

第三部分:管理用戶授權(quán)狀態(tài)

之前我們用js方法的時(shí)候,還記得嗎,我們使用的是localstorage的形式,存在了客戶端,包括用戶信息,令牌,過期時(shí)間等等,然后通過JSRuntime來實(shí)現(xiàn)對js的控制和使用,那今天我們不用js了,如何來管控呢,我這里用的是內(nèi)存緩存的形式,當(dāng)然你可以使用Redis來實(shí)現(xiàn)分布式,思路都一樣。

用戶數(shù)據(jù)存儲cache

在上邊的登錄的時(shí)候,我們看到了,每次登錄成功回調(diào)的時(shí)候,都會刷新頁面,也當(dāng)然會執(zhí)行OnGet()方法,這樣,就會把當(dāng)然用戶的信息,通過特定的sid作為緩存key的形式來保存到內(nèi)存里,這個(gè)sid就像是session一樣,每次登錄成功回調(diào)后,都會有一個(gè)唯一的字符串,作為標(biāo)識,開發(fā)過微信的應(yīng)該都知道。

那就定義一個(gè)cache管理類:

public class AuthStateCache{private ConcurrentDictionary<string, ServerAuthModel> Cache= new ConcurrentDictionary<string, ServerAuthModel>();public bool HasSubjectId(string subjectId)=> Cache.ContainsKey(subjectId);public void Add(string subjectId, DateTimeOffset expiration, string accessToken, string refreshToken){System.Diagnostics.Debug.WriteLine($"Caching sid: {subjectId}");var data = new ServerAuthModel{SubjectId = subjectId,Expiration = expiration,AccessToken = accessToken,RefreshToken = refreshToken};Cache.AddOrUpdate(subjectId, data, (k, v) => data);}public ServerAuthModel Get(string subjectId){Cache.TryGetValue(subjectId, out var data);return data;}public void Remove(string subjectId){System.Diagnostics.Debug.WriteLine($"Removing sid: {subjectId}");Cache.TryRemove(subjectId, out _);}}

這個(gè)很簡單,就不多說了,就是對用戶數(shù)據(jù)的增刪改查,標(biāo)識就是sid。那現(xiàn)在就有了一個(gè)問題,我們知道,登錄的時(shí)候是存到cache里的,那什么時(shí)候刪除呢?

請往下看。

AuthenticationStateProvider 服務(wù)

這個(gè)服務(wù)是今天的重頭戲,你需要好好的了解一下它的作用:

內(nèi)置的 AuthenticationStateProvider 服務(wù)可從 ASP.NET Core 的 HttpContext.User 獲取身份驗(yàn)證狀態(tài)數(shù)據(jù)。 身份驗(yàn)證狀態(tài)就是這樣與現(xiàn)有 ASP.NET Core 身份驗(yàn)證機(jī)制集成。

AuthenticationStateProvider 服務(wù)可以提供當(dāng)前用戶的 ClaimsPrincipal 數(shù)據(jù)。

簡單的概況呢,就是開啟這個(gè)服務(wù),我們可以獲取當(dāng)前用戶的claim聲明,并且定期的做一個(gè)篩查,就像是一個(gè)定時(shí)器,每十秒執(zhí)行一次,判斷當(dāng)前用戶是否過期,如果正好過期了,就把這個(gè)cache記錄給刪掉。

/// <summary>/// 配置狀態(tài)服務(wù)處理器,定時(shí)校驗(yàn)授權(quán)狀態(tài)/// RevalidationInterval為刷新時(shí)間,類似于滑動時(shí)間/// </summary>public class AuthStateHandler : RevalidatingServerAuthenticationStateProvider{private readonly AuthStateCache Cache;public AuthStateHandler(ILoggerFactory loggerFactory,AuthStateCache cache): base(loggerFactory){Cache = cache;}protected override TimeSpan RevalidationInterval=> TimeSpan.FromSeconds(10); // TODO read from configprotected override Task<bool> ValidateAuthenticationStateAsync(AuthenticationState authenticationState, CancellationToken cancellationToken){var sid =authenticationState.User.Claims.Where(c => c.Type.Equals("sid")).Select(c => c.Value).FirstOrDefault();if (sid != null && Cache.HasSubjectId(sid)){var data = Cache.Get(sid);System.Diagnostics.Debug.WriteLine($"NowUtc: {DateTimeOffset.UtcNow.ToString("o")}");System.Diagnostics.Debug.WriteLine($"ExpUtc: {data.Expiration.ToString("o")}");if(DateTimeOffset.UtcNow >= data.Expiration){System.Diagnostics.Debug.WriteLine($"*** EXPIRED ***");Cache.Remove(sid);return Task.FromResult(false);}}else{System.Diagnostics.Debug.WriteLine($"(not in cache)");}return Task.FromResult(true);}}

思路就是這樣,自己應(yīng)該能看明白,就是定時(shí)做了一個(gè)判斷,然后刪除cache。

服務(wù)注冊容器

把上邊的兩個(gè)服務(wù)注冊下:

?//?第三部分:授權(quán)狀態(tài)的保護(hù)與管理services.AddSingleton<AuthStateCache>();// 開啟AuthenticationStateProvider 服務(wù)services.AddScoped<AuthenticationStateProvider,?AuthStateHandler>();

第四部分:獲取token,訪問api

這一塊和之前的邏輯是一樣的,通過HttpClient來實(shí)現(xiàn)對第三方資源服務(wù)器的api訪問,那肯定需要獲取token,這個(gè)就從上邊的cache中獲取:

public async Task<string> GetAccessToken(){// 注意這獲取聲明數(shù)據(jù)有問題,參考我的代碼。獲取當(dāng)前用戶的sid唯一標(biāo)志var sid = _accessor.HttpContext.User.Claims.Where(c => c.Type.Equals("sid")).Select(c => c.Value).FirstOrDefault();// 正常,則返回結(jié)果if (sid != null && _cache.HasSubjectId(sid)){return _cache.Get(sid).AccessToken;}// 否則,跳轉(zhuǎn)登錄頁,去認(rèn)證中心拉取_navigationManager.NavigateTo("/Login", true);return?await?Task.FromResult(string.Empty);}

到了這里,我們的Blazor.Server服務(wù)端集成Ids4已經(jīng)完成了,是不是完全沒用到任何的js,來查看下效果吧:

可以看到完成了這樣的流程:

首頁不需要權(quán)限;

博客操作頁需要登錄,并成功跳轉(zhuǎn)認(rèn)證中心;

登錄后,成功回調(diào)到首頁,并獲取用戶信息;

實(shí)現(xiàn)單點(diǎn)登錄;

編輯的時(shí)候,test用戶返回Forbidden,表明已經(jīng)登錄,并實(shí)現(xiàn)了權(quán)限控制;

好啦,自己動手試試吧。

參考文章:

1、https://mcguirev10.com/2019/12/15/blazor-authentication-with-openid-connect.html

2、https://github.com/BlazorHub/AntDesignTemplate

總結(jié)

以上是生活随笔為你收集整理的Blazor.Server以正确的方式集成Ids4的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。