在identityServer4中登陸頁面只要是成功了,就會註冊一個Cookie在伺服器資源上,像現在大部分的網站第三方授權,都是經過一個頁面,然後選需要的功能,IdentityServer4也給我們提供了,只要你登陸成功,就會跳轉到Consent/Index(Get)中,所以我們只要在其中做手腳就 ...
在identityServer4中登陸頁面只要是成功了,就會註冊一個Cookie在伺服器資源上,像現在大部分的網站第三方授權,都是經過一個頁面,然後選需要的功能,IdentityServer4也給我們提供了,只要你登陸成功,就會跳轉到Consent/Index(Get)中,所以我們只要在其中做手腳就好了。
在編寫代碼之前我們要知道IdentityServer的三個介面, IClientStore 是存放客戶端信息的, IResourceStore 是存放資源API信息的,這兩個介面都是在IdentityServer4的Stores的命名空間下,還有一個介面是 IIdentityServerInteractionService 用於與IdentityServer通信的服務,主要涉及用戶交互。它可以從依賴註入系統獲得,通常作為構造函數參數註入到IdentityServer的用戶界面的MVC控制器中。
下麵我們創建一個Consent控制器在認證伺服器上,名為 ConsentController ,在其中我們需要將這三個介面通過構造函數構造進來。
public class ConsentController : Controller { private readonly IClientStore _clientStore; private readonly IResourceStore _resourceStore; private readonly IIdentityServerInteractionService _identityServerInteractionService; public ConsentController( IClientStore clientStore, IResourceStore resourceStore, IIdentityServerInteractionService identityServerInteractionService) { _clientStore = clientStore; _resourceStore = resourceStore; _identityServerInteractionService = identityServerInteractionService; } }
在控制器中,因為登陸成功是從Account控制器調過來的,那個時候還帶著ReturnUrl這個而參數,我們在這個控制器中也需要ReturnUrl,所以在Get方法中寫上該參數,要不然跳轉不過來的。
public async Task<IActionResult> Index(string returnUrl) { var model =await BuildConsentViewModel(returnUrl);return View(model); }
其中調用了 BuildConsentViewModel 方法用於返回一個consent對象,其中我們使用 _identityServerInteractionService 介面獲取了上下文,然後再通過其餘的兩個介面找到它客戶端還有資源api的信息。然後再調用了自定義的 CreateConsentViewModel 對象創建了consent對象。
/// <summary> /// 返回一個consent對象 /// </summary> private async Task<ConsentVm> BuildConsentViewModel(string returlUrl) { //獲取驗證上下文 var request = await _identityServerInteractionService.GetAuthorizationContextAsync(returlUrl); if (request == null) return null; //根據上下文獲取client的信息以及資源Api的信息 var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested); //創建consent對象 var vm = CreateConsentViewModel(request,client,resources); vm.ReturnUrl = returlUrl; return vm; }
在其中創建對象並返回,只不過在獲取ResourceScopes的時候,它是一個ApiResource,所以需要先轉換成Scopes然呢再Select一下變成我們的ViewModel.
/// <summary> /// 創建consent對象 /// </summary> private ConsentVm CreateConsentViewModel(AuthorizationRequest request,Client client,Resources resources) { var vm = new ConsentVm(); vm.ClientId = client.ClientId; vm.Logo = client.LogoUri; vm.ClientName = client.ClientName; vm.ClientUrl = client.ClientUri;//客戶端url vm.RemeberConsent = client.AllowRememberConsent;//是否記住信息 vm.IdentityScopes = resources.IdentityResources.Select(i=>CreateScopeViewModel(i)); vm.ResourceScopes = resources.ApiResources.SelectMany(u => u.Scopes).Select(x => CreatesScoreViewModel(x)); return vm; } public ScopeVm CreatesScoreViewModel(Scope scope) { return new ScopeVm { name = scope.Name, DisplayName = scope.DisplayName, Description = scope.Description, Required = scope.Required, Checked = scope.Required, Emphasize = scope.Emphasize }; } private ScopeVm CreateScopeViewModel(IdentityResource identityResource) { return new ScopeVm { name = identityResource.Name, DisplayName = identityResource.DisplayName, Description = identityResource.Description, Required = identityResource.Required, Checked = identityResource.Required, Emphasize = identityResource.Emphasize }; }
以上我們的控制器就完成了,現在我們搞一下視圖,在視圖中我們就是簡單做一下,使用ConsentVm作為視圖綁定對象,在之中我遇到了一個Bug,我用 @Html.Partial("_ScopeListItem", item); 的時候突然就報錯了,在頁面上顯示一個Task一大堆的錯誤信息,我也不知道啥情況(望大佬解決),換成不是非同步的就行了。
<p>Consent Page</p> @using mvcWebFirstSolucation.Models; @model ConsentVm <div class="row page-header"> <div class="col-sm-10"> @if (!string.IsNullOrWhiteSpace(Model.Logo)) { <div> <img src="@Model.Logo" /> </div> } <h1> @Model.ClientName <small>歡迎來到第三方授權</small> </h1> </div> </div> <div class="row"> <div class="col-sm-8"> <form asp-action="Index"> <input type="hidden" asp-for="ReturnUrl" /> <div class="panel"> <div class="panel-heading"> <span class="glyphicon glyphicon-tasks"></span> 用戶信息 </div> <ul class="list-group"> @foreach (var item in Model.IdentityScopes) { @Html.Partial("_ScopeListItem", item); } </ul> </div> <div class="panel"> <div class="panel-heading"> <span class="glyphicon glyphicon-tasks"></span> 應用許可權 </div> <ul class="list-group"> @foreach (var item in Model.ResourceScopes) { @Html.Partial("_ScopeListItem", item); } </ul> </div> <div> <label> <input type="checkbox" asp-for="RemeberConsent" /> <strong>記住我的選擇</strong> </label> </div> <div> <button value="yes" class="btn btn-primary" name="button" autofocus>同意</button> <button value="no" name="button">取消</button> @if (!string.IsNullOrEmpty(Model.ClientUrl)) { <a href="@Model.ClientUrl" class="pull-right btn btn-default"> <span class="glyphicon glyphicon-info-sign"></span> <strong>@Model.ClientUrl</strong> </a> } </div> </form> </div> </div>
下麵是局部視圖的定義,傳過來的對象是 ResourceScopes 和 IdentityScopes ,但他們都是對應ScopeVm,在其中呢就是把他們哪些許可權列出來,然後勾選,在它的父頁面已經做了post提交,所以我們還得弄個控制器。
@using mvcWebFirstSolucation.Models; @model ScopeVm <li> <label> <input type="checkbox" name="ScopesConsented" id="[email protected]" value="@Model.name" checked="@Model.Checked" disabled="@Model.Required"/> @if (Model.Required) { <input type="hidden" name="ScopesConsented" value="@Model.name" /> } <strong>@Model.name</strong> @if (Model.Emphasize) { <span class="glyphicon glyphicon-exclamation-sign"></span> } </label> @if (!string.IsNullOrEmpty(Model.Description)) { <div> <label for="[email protected]">@Model.Description</label> </div> } </li>
這個方法的參數是我們所自定義的實體,其中有按鈕還有返回的地址,在其中我們判斷了是否選擇OK,選擇不那就直接賦一個拒絕的指令,如果ok那麼就直接判斷是否有這個權力,因為我們在config中進行了配置,然後如果有,呢麽就直接添加,在不==null的清空下,我們根據 returlUrl 這個字元串獲取了請求信息,然後通過 GrantConsentAsync 方法直接同意了授權,然後直接跳轉過去,就成功了。
[HttpPost] public async Task<IActionResult> Index(InputConsentViewModel viewmodel) { // viewmodel.ReturlUrl ConsentResponse consentResponse = null; if (viewmodel.Button =="no") { consentResponse = ConsentResponse.Denied; } else { if (viewmodel.ScopesConsented !=null && viewmodel.ScopesConsented.Any()) { consentResponse = new ConsentResponse { RememberConsent = viewmodel.RemeberConsent, ScopesConsented = viewmodel.ScopesConsented }; } } if (consentResponse != null) { var request = await _identityServerInteractionService.GetAuthorizationContextAsync(viewmodel.ReturnUrl); await _identityServerInteractionService.GrantConsentAsync(request, consentResponse); return Redirect(viewmodel.ReturnUrl); } return View(await BuildConsentViewModel(viewmodel.ReturnUrl)); }
最後,在調試的時候一定要Client的 RequireConsent 設置為true.