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

 .NET Core IdentityServer4实战 第六章-Consent授权页-LMLPHP

07-07 20:06