1. 程式人生 > >.NET Core IdentityServer4實戰 第六章-Consent授權頁

.NET Core IdentityServer4實戰 第六章-Consent授權頁

  在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.

 

&n