欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-13 14:26:28
在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="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.

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