.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="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授权页
-
【.NET Core项目实战-统一认证平台】第九章 授权篇-使用Dapper持久化IdentityServer4
-
Asp.Net Core 中IdentityServer4 授权中心之应用实战
-
【.NET Core项目实战-统一认证平台】第六章 网关篇-自定义客户端授权
-
Asp.Net Core 中IdentityServer4 实战之角色授权详解
-
【.NET Core项目实战-统一认证平台】第八章 授权篇-IdentityServer4源码分析
-
.NET Core IdentityServer4实战 第六章-Consent授权页
-
Asp.Net Core 中IdentityServer4 授权中心之应用实战
-
【.NET Core项目实战-统一认证平台】第九章 授权篇-使用Dapper持久化IdentityServer4
-
【.NET Core项目实战-统一认证平台】第六章 网关篇-自定义客户端授权