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


浙公网安备 33010602011771号