tnblog
首页
视频
资源
登录

.net core 3.1 Identity Server4 (添加同意范围页)

5690人阅读 2021/1/13 15:13 总访问:3661473 评论:1 收藏:0 手机
分类: Ids4

.netcore

.net core 3.1 Identity Server4 (添加同意范围页)

在授权请求期间,如果身份服务器需要用户同意,浏览器将被重定向到同意页面。也就是说,确认也算是IdentityServer中的一个动作。确认这个词直接翻译过来有一些古怪,既然大家都知道Consent就是确认的意思,下文都以Consent来指代确认。
Consent被用来允许终端用户将一些资源(例如identity 和 API)的访问权限授予客户端。这通常适用于一些第三方应用,并且可以在 client settings中对每个客户端进行这方面的设置。

创建ConsentResourceController(同意控制器)

  1. [Route("Consent")]
  2. public class ConsentResourceController : Controller
  3. {
  4. [HttpGet]
  5. public async Task<IActionResult> Index(string returnUrl)
  6. {
  7. var model = await BuildConsentViewModel(returnUrl);
  8. if (model == null)
  9. {
  10. }
  11. return View(model);
  12. }
  13. }

这里的BuildConsentViewModel方法主要是用来获取同意页面上需要的内容。我们先来看看视图大概会有哪些?

  1. @using AiDaSi.OcDemo.Authenzation.Model
  2. @model ConsentResourceViewModel
  3. <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  4. <H1>Consent Page</H1>
  5. <div class="row">
  6. <div class="row page-header">
  7. <div class="col-sm-2">
  8. @if (!string.IsNullOrWhiteSpace(Model.ClientLogoUrl))
  9. {
  10. <div> <img src="@Model.ClientLogoUrl" /> </div>
  11. }
  12. </div>
  13. <h1>
  14. @Model.ClientName
  15. <small>We wish using your account</small>
  16. </h1>
  17. </div>
  18. </div>
  19. <div class="row">
  20. <div class="col-sm-8">
  21. <form asp-action="Index">
  22. <input type="hidden" asp-for="RedirectUri" />
  23. @if (Model.IdentityScopes.Any())
  24. {
  25. <div class="form-group">
  26. <div class="card">
  27. <div class="card-header">
  28. <span class="glyphicon glyphicon-tasks"></span>
  29. Personal Information
  30. </div>
  31. <ul class="list-group list-group-flush">
  32. @foreach (var scope in Model.IdentityScopes)
  33. {
  34. @await Html.PartialAsync("_ScopeListitem", scope)
  35. }
  36. </ul>
  37. </div>
  38. </div>
  39. }
  40. @if (Model.ResourceScopes.Any())
  41. {
  42. <div class="form-group">
  43. <div class="card">
  44. <div class="card-header">
  45. <span class="glyphicon glyphicon-tasks"></span>
  46. Application Access
  47. </div>
  48. <ul class="list-group list-group-flush">
  49. @foreach (var scope in Model.ResourceScopes)
  50. {
  51. @await Html.PartialAsync("_ScopeListitem", scope)
  52. }
  53. </ul>
  54. </div>
  55. </div>
  56. }
  57. <div>
  58. <label>
  59. <input type="checkbox" asp-for="RemeberConsent" />
  60. <strong>记住我的选择</strong>
  61. </label>
  62. </div>
  63. <div>
  64. <button name="button" value="yes" class="btn btn-primary" autofocus>同意</button>
  65. <button name="button" value="no" >取消</button>
  66. @if (!string.IsNullOrEmpty(Model.ClientLogoUrl))
  67. {
  68. <a>
  69. <span class="glyphicon glyphicon-info-sign"></span>
  70. <strong>@Model.ClientUrl</strong>
  71. </a>
  72. }
  73. </div>
  74. </form>
  75. </div>
  76. </div>

大概有客户端的logo(ClientLogoUrl)、客户端名称(ClientName)、返回链接(RedirectUri)、身份认证资源(IdentityScopes)、API资源(ResourceScopes)、是否记住(RemeberConsent)、客户端链接(ClientUrl)。。。在_ScopeListitem页面中是展示资源集合。

  1. @using AiDaSi.OcDemo.Authenzation.Model
  2. @model ScopeViewModel
  3. <li class="list-group-item">
  4. <label>
  5. <input class="consent-scopecheck"
  6. type="checkbox"
  7. name="ScopesConsented"
  8. id="scopes_@Model.Name"
  9. value="@Model.Name"
  10. checked="@Model.Checked"
  11. disabled="@Model.Required" />
  12. @if (Model.Required)
  13. {
  14. <input type="hidden"
  15. name="ScopesConsented"
  16. value="@Model.Name" />}
  17. <strong>@Model.DisplayName</strong>
  18. @if (Model.Emphasize)
  19. {
  20. <span class="glyphicon glyphicon-exclamation-sign"></span>}
  21. </label>
  22. @if (Model.Required)
  23. {
  24. <span><em>(required)</em></span>}
  25. @if (Model.Discription != null)
  26. {
  27. <div class="consent-description">
  28. <label for="scopes_@Model.Name">@Model.Discription</label>
  29. </div>}
  30. </li>

Name是api资源名称,DisplayName是显示出这个资源名称,Checked是是否选中,Required是否是必需的,Discription是说明。关于Model的类如下:

  1. /// <summary>
  2. /// 资源范围
  3. /// </summary>
  4. public class ScopeViewModel
  5. {
  6. public string Name { get; set; }
  7. public string DisplayName { get; set; }
  8. /// <summary>
  9. /// 描述
  10. /// </summary>
  11. public string Discription { get; set; }
  12. /// <summary>
  13. /// 是否强调
  14. /// </summary>
  15. public bool Emphasize { get; set; }
  16. /// <summary>
  17. /// 是否选中
  18. /// </summary>
  19. public bool Checked { get; set; }
  20. /// <summary>
  21. /// 是不是必需的
  22. /// </summary>
  23. public bool Required { get; set; }
  24. }

关于同意页面需要的Model如下:

  1. /// <summary>
  2. /// 同意视图模型
  3. /// </summary>
  4. public class ConsentResourceViewModel: InputConsentViewModel
  5. {
  6. public string ClientId { get; set; }
  7. public string ClientName { get; set; }
  8. /// <summary>
  9. /// 客户端图标
  10. /// </summary>
  11. public string ClientLogoUrl { get; set; }
  12. /// <summary>
  13. /// 客户端地址
  14. /// </summary>
  15. public string ClientUrl { get; set; }
  16. /// <summary>
  17. /// 身份认证资源范围
  18. /// </summary>
  19. public IEnumerable<ScopeViewModel> IdentityScopes { get; set; }
  20. /// <summary>
  21. /// 资源范围
  22. /// </summary>
  23. public IEnumerable<ScopeViewModel> ResourceScopes { get; set; }
  24. /// <summary>
  25. /// 确认是否需要记住
  26. /// </summary>
  27. public bool RemeberConsent { get; set; }
  28. /// <summary>
  29. /// 客户端地址
  30. /// </summary>
  31. public string RedirectUri { get; set; }
  32. }

接着我们在ConsentResourceController.cs添加相关的代码创建对应页面的实例

  1. private readonly IClientStore _clientStore;
  2. private readonly IResourceStore _resourceStore;
  3. private readonly IIdentityServerInteractionService _identityServerInteractionService;
  4. public ConsentResourceController(
  5. IClientStore clientStore,
  6. IResourceStore resourceStore,
  7. IIdentityServerInteractionService identityServerInteractionService
  8. )
  9. {
  10. _clientStore = clientStore;
  11. _resourceStore = resourceStore;
  12. _identityServerInteractionService = identityServerInteractionService;
  13. }
  14. private async Task<ConsentResourceViewModel> BuildConsentViewModel(string returnUrl)
  15. {
  16. // 获取授权上下文
  17. var request =await _identityServerInteractionService.GetAuthorizationContextAsync(returnUrl);
  18. if (request == null)
  19. {
  20. return null;
  21. }
  22. // 获取客户端
  23. var client = await _clientStore.FindEnabledClientByIdAsync(request.Client.ClientId);
  24. // 获取资源
  25. // var IdentityScopes = request.ValidatedResources.Resources.IdentityResources;
  26. var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.Client.AllowedScopes);
  27. var vm = CreateConsentResourceViewModel(request, client, resources);
  28. vm.RedirectUri = returnUrl;
  29. return vm;
  30. }
  31. private ConsentResourceViewModel CreateConsentResourceViewModel(AuthorizationRequest request,Client client,Resources resource)
  32. {
  33. // 赋值
  34. var vm = new ConsentResourceViewModel();
  35. vm.ClientName = client.ClientName;
  36. vm.ClientLogoUrl = client.LogoUri;
  37. vm.ClientUrl = client.ClientUri;
  38. vm.RemeberConsent = client.AllowRememberConsent;
  39. vm.IdentityScopes = resource.IdentityResources.Select(i => CreateScopeViewModel(i));
  40. vm.ResourceScopes = resource.ApiScopes.Select(x => CreateScopeViewModel(x));
  41. return vm;
  42. }
  43. /// <summary>
  44. /// 创建身份资源实例
  45. /// </summary>
  46. /// <param name="identityResource"></param>
  47. /// <returns></returns>
  48. private ScopeViewModel CreateScopeViewModel(IdentityResource identityResource)
  49. {
  50. return new ScopeViewModel()
  51. {
  52. Name = identityResource.Name,
  53. DisplayName = identityResource.DisplayName,
  54. Discription = identityResource.Description,
  55. Required = identityResource.Required,
  56. Checked = identityResource.Required,
  57. Emphasize= identityResource.Emphasize
  58. };
  59. }
  60. /// <summary>
  61. /// 创建api资源
  62. /// </summary>
  63. /// <param name="identityResource"></param>
  64. /// <returns></returns>
  65. private ScopeViewModel CreateScopeViewModel(ApiScope identityResource)
  66. {
  67. return new ScopeViewModel()
  68. {
  69. Name = identityResource.Name,
  70. DisplayName = identityResource.DisplayName,
  71. Discription = identityResource.Description,
  72. Required = identityResource.Required,
  73. Checked = identityResource.Required,
  74. Emphasize = identityResource.Emphasize
  75. };
  76. }

运行启动一下

问题来了,这些客户端地址与图片地址是从哪儿获取的,而且根本就访问不了这个地址。其实都在ConfigClient设置


创建提交实例

  1. public class InputConsentViewModel
  2. {
  3. /// <summary>
  4. /// 确认按钮与取消按钮(同意页面)
  5. /// </summary>
  6. public string Button { get; set; }
  7. public IEnumerable<string> ScopesConsented { get; set; }
  8. /// <summary>
  9. /// 确认是否需要记住
  10. /// </summary>
  11. public bool RemeberConsent { get; set; }
  12. /// <summary>
  13. /// 客户端地址
  14. /// </summary>
  15. public string RedirectUri { get; set; }
  16. }

最后在控制器中添加提交所对应的方法

  1. [HttpPost]
  2. public async Task<IActionResult> Index(InputConsentViewModel viewModel)
  3. {
  4. ConsentResponse consentResponse = null;
  5. if (viewModel.Button == "no")
  6. {
  7. consentResponse = new ConsentResponse{ Error = AuthorizationError.AccessDenied };
  8. }
  9. else if(viewModel.Button =="yes")
  10. {
  11. if (viewModel.ScopesConsented != null && viewModel.ScopesConsented.Any())
  12. {
  13. // 前端相关范围以及是否需要记住该账号
  14. consentResponse = new ConsentResponse {
  15. ScopesValuesConsented = viewModel.ScopesConsented,
  16. RememberConsent = viewModel.RemeberConsent
  17. };
  18. }
  19. }
  20. if (consentResponse != null)
  21. {
  22. var request = await _identityServerInteractionService.GetAuthorizationContextAsync(viewModel.RedirectUri);
  23. await _identityServerInteractionService.GrantConsentAsync(request, consentResponse);
  24. // 完成
  25. return Redirect(viewModel.RedirectUri);
  26. }
  27. return View();
  28. }

当完成这一系列的操作后会看到登录成功的页面

重构代码,添加验证

创建相关Services

添加的代码是直接从控制器中搬过来的但也做了一些改动

  1. public class ConsentService
  2. {
  3. private readonly IClientStore _clientStore;
  4. private readonly IResourceStore _resourceStore;
  5. private readonly IIdentityServerInteractionService _identityServerInteractionService;
  6. public ConsentService(
  7. IClientStore clientStore,
  8. IResourceStore resourceStore,
  9. IIdentityServerInteractionService identityServerInteractionService
  10. )
  11. {
  12. _clientStore = clientStore;
  13. _resourceStore = resourceStore;
  14. _identityServerInteractionService = identityServerInteractionService;
  15. }
  16. public async Task<ConsentResourceViewModel> BuildConsentViewModel(string returnUrl, InputConsentViewModel inputConsentViewModel = null)
  17. {
  18. // 获取授权上下文
  19. var request = await _identityServerInteractionService.GetAuthorizationContextAsync(returnUrl);
  20. if (request == null)
  21. {
  22. return null;
  23. }
  24. // 获取客户端
  25. var client = await _clientStore.FindEnabledClientByIdAsync(request.Client.ClientId);
  26. // 获取资源
  27. // var IdentityScopes = request.ValidatedResources.Resources.IdentityResources;
  28. var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.Client.AllowedScopes);
  29. var vm = CreateConsentResourceViewModel(request, client, resources, inputConsentViewModel);
  30. vm.RedirectUri = returnUrl;
  31. return vm;
  32. }
  33. public async Task<ProcessConsentResult> PorcessConsent(InputConsentViewModel viewModel)
  34. {
  35. ConsentResponse consentResponse = null;
  36. var result = new ProcessConsentResult();
  37. if (viewModel.Button == "no")
  38. {
  39. consentResponse = new ConsentResponse { Error = AuthorizationError.AccessDenied };
  40. }
  41. else if (viewModel.Button == "yes")
  42. {
  43. if (viewModel.ScopesConsented != null && viewModel.ScopesConsented.Any())
  44. {
  45. // 前端相关范围以及是否需要记住该账号
  46. consentResponse = new ConsentResponse
  47. {
  48. ScopesValuesConsented = viewModel.ScopesConsented,
  49. RememberConsent = viewModel.RemeberConsent
  50. };
  51. }
  52. result.ValidationError = "请至少选中一个权限";
  53. }
  54. if (consentResponse != null)
  55. {
  56. var request = await _identityServerInteractionService.GetAuthorizationContextAsync(viewModel.RedirectUri);
  57. await _identityServerInteractionService.GrantConsentAsync(request, consentResponse);
  58. // 完成
  59. result.RedirectUrl = viewModel.RedirectUri;
  60. }
  61. {
  62. var consentViewModel = await BuildConsentViewModel(viewModel.RedirectUri, viewModel);
  63. result.ViewModel = consentViewModel;
  64. }
  65. return result;
  66. }
  67. #region Private_Method
  68. private ConsentResourceViewModel CreateConsentResourceViewModel(AuthorizationRequest request, Client client, Resources resource,InputConsentViewModel inputConsentViewModel)
  69. {
  70. var remeberConsent = inputConsentViewModel?.RemeberConsent ?? true;
  71. var selectedScopes = inputConsentViewModel?.ScopesConsented ?? Enumerable.Empty<string>();
  72. // 赋值
  73. var vm = new ConsentResourceViewModel();
  74. vm.ClientName = client.ClientName;
  75. vm.ClientLogoUrl = client.LogoUri;
  76. vm.ClientUrl = client.ClientUri;
  77. vm.RemeberConsent = remeberConsent;
  78. vm.IdentityScopes = resource.IdentityResources.Select(i => CreateScopeViewModel(i, selectedScopes.Contains(i.Name) || inputConsentViewModel == null) );
  79. vm.ResourceScopes = resource.ApiScopes.Select(x => CreateScopeViewModel(x, selectedScopes.Contains(x.Name) || inputConsentViewModel == null));
  80. return vm;
  81. }
  82. /// <summary>
  83. /// 创建身份资源实例
  84. /// </summary>
  85. /// <param name="identityResource"></param>
  86. /// <returns></returns>
  87. private ScopeViewModel CreateScopeViewModel(IdentityResource identityResource,bool check)
  88. {
  89. return new ScopeViewModel()
  90. {
  91. Name = identityResource.Name,
  92. DisplayName = identityResource.DisplayName,
  93. Discription = identityResource.Description,
  94. Required = identityResource.Required,
  95. Checked = check || identityResource.Required,
  96. Emphasize = identityResource.Emphasize
  97. };
  98. }
  99. /// <summary>
  100. /// 创建api资源
  101. /// </summary>
  102. /// <param name="identityResource"></param>
  103. /// <returns></returns>
  104. private ScopeViewModel CreateScopeViewModel(ApiScope identityResource, bool check)
  105. {
  106. return new ScopeViewModel()
  107. {
  108. Name = identityResource.Name,
  109. DisplayName = identityResource.DisplayName,
  110. Discription = identityResource.Description,
  111. Required = identityResource.Required,
  112. Checked = check || identityResource.Required,
  113. Emphasize = identityResource.Emphasize
  114. };
  115. }
  116. #endregion
  117. }

BuildConsentViewModel方法中,我们新添加了一个InputConsentViewModel参数。主要用于对记住选项,记住选择的Scope,最后在CreateScopeViewModelCreateScopeViewModel中为选中的Checked字段赋值


封装了ConsentResourceController中对提交了的数据处理,这里如果scope的选中数量小于1,我们就判断它为验证失败

这里我们看一下ProcessConsentResult模型实例内容

  1. public class ProcessConsentResult
  2. {
  3. /// <summary>
  4. /// 返回Url连接的地址
  5. /// </summary>
  6. public string RedirectUrl { get; set; }
  7. /// <summary>
  8. /// 判断返回地址是否为空
  9. /// </summary>
  10. public bool IsRedirect => RedirectUrl != null;
  11. /// <summary>
  12. /// 提交的实例
  13. /// </summary>
  14. public ConsentResourceViewModel ViewModel { get; set; }
  15. /// <summary>
  16. /// 是否有验证失败
  17. /// </summary>
  18. public string ValidationError { get; set; }
  19. }

然后在控制器中实现对返回的连接地址进行判断,如果没有返回地址,就对验证字符串进行非空判断,如果有错就报错。

  1. [HttpPost]
  2. public async Task<IActionResult> Index(InputConsentViewModel viewModel)
  3. {
  4. var result = await _consentService.PorcessConsent(viewModel);
  5. if (result.IsRedirect)
  6. {
  7. // 完成
  8. return Redirect(result.RedirectUrl);
  9. }
  10. if (!string.IsNullOrEmpty(result.ValidationError))
  11. {
  12. ModelState.AddModelError("", result.ValidationError);
  13. }
  14. return View(result.ViewModel);
  15. }

随后需要在前台页面添加显示错误的代码

  1. <div class="alert alert-danger">
  2. <strong>Error""</strong>
  3. <div asp-validation-summary="All" class="danger"></div>
  4. </div>

最后需要在Startup.cs中添加ConsentService的服务注入

  1. services.AddScoped<ConsentService>();

运行一下,首先我们看见所有的都是被选中的还有openid是必选的。

我们将这些沟都去掉包括openid的(去掉它的disabled="disabled"



我们再点同意,它将会报错!

我们还可以再对前端的验证那儿做一下优化,然后一开始它就不会再出现了

  1. @if (!ViewContext.ModelState.IsValid)
  2. {
  3. <div class="alert alert-danger">
  4. <strong>Error""</strong>
  5. <div asp-validation-summary="All" class="danger"></div>
  6. </div>
  7. }

解决取消按钮后的跳转问题

当我们点击取消时,我们发现它报错了,内容如下:

我们要解决这个问题的话可以直接从客户端中OnAccessDenied事件进行处理。

  1. options.Events.OnAccessDenied = async (x) =>
  2. {
  3. x.HttpContext.Response.StatusCode = StatusCodes.Status200OK;
  4. x.HttpContext.Response.ContentType = "text/html";
  5. await x.HttpContext.Response.WriteAsync("You have cancelled the login, please access the client address: https://localhost:6027/");
  6. };


欢迎加群讨论技术,1群:677373950(满了,可以加,但通过不了),2群:656732739

评价

尘叶心繁

2021/1/13 15:32:21

同意范围页在consent分支里面的

net core 使用 EF Code First

下面这些内容很老了看这篇:https://www.tnblog.net/aojiancc2/article/details/5365 项目使用多层,把数据库访问...

.net mvc分部页,.net core分部页

.net分部页的三种方式第一种:@Html.Partial(&quot;_分部页&quot;)第二种:@{ Html.RenderPartial(&quot;分部页&quot;);}...

StackExchange.Redis操作redis(net core支持)

官方git开源地址https://github.com/StackExchange/StackExchange.Redis官方文档在docs里边都是官方的文档通过nuget命令下...

.net core 使用session

tip:net core 2.2后可以直接启用session了,不用在自己添加一次session依赖,本身就添加了使用nuget添加引用Microsoft.AspN...

通俗易懂,什么是.net?什么是.net Framework?什么是.net core?

朋友圈@蓝羽 看到一篇文章写的太详细太通俗了,搬过来细细看完,保证你对.NET有个新的认识理解原文地址:https://www.cnblo...

asp.net core2.0 依赖注入 AddTransient与AddScoped的区别

asp.net core主要提供了三种依赖注入的方式其中AddTransient与AddSingleton比较好区别AddTransient瞬时模式:每次都获取一...

.net core 使用 Kestrel

Kestrel介绍 Kestrel是一个基于libuv的跨平台web服务器 在.net core项目中就可以不一定要发布在iis下面了Kestrel体验可以使...

net core中使用cookie

net core中可以使用传统的cookie也可以使用加密的cookieNET CORE中使用传统cookie设置:HttpContext.Response.Cookies.Appe...

net core项目结构简单分析

一:wwwrootwwwroot用于存放网站的静态资源,例如css,js,图片与相关的前端插件等lib主要是第三方的插件,例如微软默认引用...

net core使用EF之DB First

一.新建一个.net core的MVC项目新建好项目后,不能像以前一样直接在新建项中添加ef了,需要用命令在添加ef的依赖二.使用Nug...

.net core使用requestresponse下载文件下载excel等

使用request获取内容net core中request没有直接的索引方法,需要点里边的Query,或者formstringbase64=Request.Form[&quot;f...

iframe自适应高度与配合net core使用

去掉iframe边框frameborder=&quot;0&quot;去掉滚动条scrolling=&quot;no&quot;iframe 自适应高度如果内容是固定的,那么就...

net core启动报错Unable to configure HTTPS endpoint. No server certificate was specified

这是因为net core2.1默认使用的https,如果使用Kestrel web服务器的话没有安装证书就会报这个错其实仔细看他的错误提示,其...

net core中使用url编码与解码操作

net core中暂时还没有以前asp.net与mvc中的server对象。获取url的编码与解码操作不能使用以前的server对象来获取。使用的是...

下载net core

官方下载地址:https://dotnet.microsoft.com/download 进来之后就可以看到最新的下载版本可以直接点击下载,也可以下载其...
这一世以无限游戏为使命!
排名
2
文章
657
粉丝
44
评论
93
docker中Sware集群与service
尘叶心繁 : 想学呀!我教你呀
一个bug让程序员走上法庭 索赔金额达400亿日元
叼着奶瓶逛酒吧 : 所以说做程序员也要懂点法律知识
.net core 塑形资源
剑轩 : 收藏收藏
映射AutoMapper
剑轩 : 好是好,这个对效率影响大不大哇,效率高不高
ASP.NET Core 服务注册生命周期
剑轩 : http://www.tnblog.net/aojiancc2/article/details/167
ICP备案 :渝ICP备18016597号-1
网站信息:2018-2025TNBLOG.NET
技术交流:群号656732739
联系我们:contact@tnblog.net
公网安备:50010702506256
欢迎加群交流技术