在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="scopes_@model.name" 
               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="scopes_@model.name">@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.