Core篇——初探IdentityServer4(OpenID Connect客户端验证)

目录

1、Oauth2协议授权码模式介绍
2、IdentityServer4的OpenID Connect客户端验证简单实现

Oauth2协议授权码模式介绍

  • 授权码模式是Oauth2协议中最严格的认证模式,它的组成以及运行流程是这样
    1、用户访问客户端,客户端将用户导向认证服务器
    2、用户在认证服务器输入用户名密码选择授权,认证服务器认证成功后,跳转至一个指定好的"跳转Url",同时携带一个认证码
    3、用户携带认证码请求指定好的"跳转Url"再次请求认证服务器(这一步后台完成,对用户不可见),此时,由认证服务器返回一个Token
    4、客户端携带token请求用户资源
  • OpenId Connect运行流程为
    1、用户访问客户端,客户端将用户导向认证服务器
    2、用户在认证服务器输入用户名密码认证授权
    3、认证服务器返回token和资源信息

IdentityServer4的OpenID Connect客户端验证简单实现

Server部分

  • 添加一个Mvc项目,配置Config.cs文件
    1. public class Config
    2. {
    3. //定义要保护的资源(webapi)
    4. public static IEnumerable<ApiResource> GetApiResources()
    5. {
    6. return new List<ApiResource>
    7. {
    8. new ApiResource("api1", "My API")
    9. };
    10. }
    11. //定义可以访问该API的客户端
    12. public static IEnumerable<Client> GetClients()
    13. {
    14. return new List<Client>
    15. {
    16. new Client
    17. {
    18. ClientId = "mvc",
    19. // no interactive user, use the clientid/secret for authentication
    20. AllowedGrantTypes = GrantTypes.Implicit, //简化模式
    21. // secret for authentication
    22. ClientSecrets =
    23. {
    24. new Secret("secret".Sha256())
    25. },
    26. RequireConsent =true, //用户选择同意认证授权
    27. RedirectUris={ "http://localhost:5001/signin-oidc" }, //指定允许的URI返回令牌或授权码(我们的客户端地址)
    28. PostLogoutRedirectUris={ "http://localhost:5001/signout-callback-oidc" },//注销后重定向地址 参考https://identityserver4.readthedocs.io/en/release/reference/client.html
    29. LogoUri="https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3298365745,618961144&fm=27&gp=0.jpg",
    30. // scopes that client has access to
    31. AllowedScopes = { //客户端允许访问个人信息资源的范围
    32. IdentityServerConstants.StandardScopes.Profile,
    33. IdentityServerConstants.StandardScopes.OpenId,
    34. IdentityServerConstants.StandardScopes.Email,
    35. IdentityServerConstants.StandardScopes.Address,
    36. IdentityServerConstants.StandardScopes.Phone
    37. }
    38. }
    39. };
    40. }
    41. public static List<TestUser> GeTestUsers()
    42. {
    43. return new List<TestUser>
    44. {
    45. new TestUser
    46. {
    47. SubjectId = "",
    48. Username = "alice",
    49. Password = "password"
    50. },
    51. new TestUser
    52. {
    53. SubjectId = "",
    54. Username = "bob",
    55. Password = "password"
    56. }
    57. };
    58. }
    59. //openid connect
    60. public static IEnumerable<IdentityResource> GetIdentityResources()
    61. {
    62. return new List<IdentityResource>
    63. {
    64. new IdentityResources.OpenId(),
    65. new IdentityResources.Profile(),
    66. new IdentityResources.Email()
    67. };
    68. }
    69. }

    Config

  • 添加几个ViewModel 用来接收解析跳转URL后的参数
    1. public class InputConsentViewModel
    2. {
    3. public string Button { get; set; }
    4. public IEnumerable<string> ScopesConsented { get; set; }
    5.  
    6. public bool RemeberConsent { get; set; }
    7. public string ReturnUrl { get; set; }
    8. }
    9. //解析跳转url后得到的应用权限等信息
    10. public class ConsentViewModel:InputConsentViewModel
    11. {
    12. public string ClientId { get; set; }
    13. public string ClientName { get; set; }
    14. public string ClientUrl { get; set; }
    15. public string ClientLogoUrl { get; set; }
    16. public IEnumerable<ScopeViewModel> IdentityScopes { get; set; }
    17. public IEnumerable<ScopeViewModel> ResourceScopes { get; set; }
    18. }
    19. //接收Scope
    20. public class ScopeViewModel
    21. {
    22. public string Name { get; set; }
    23. public string DisplayName { get; set; }
    24. public string Description { get; set; }
    25. public bool Emphasize { get; set; }
    26. public bool Required { get; set; }
    27. public bool Checked { get; set; }
    28. }
    29. public class ProcessConsentResult
    30. {
    31. public string RedirectUrl { get; set; }
    32. public bool IsRedirectUrl => RedirectUrl != null;
    33. public string ValidationError { get; set; }
    34. public ConsentViewModel ViewModel { get; set; }
    35. }

    ViewModel

  • 配置StartUp,将IdentityServer加入到DI容器,这里有个ConsentService,用来处理解析跳转URL的数据,这个Service在下面实现。
  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddIdentityServer()
  4. .AddDeveloperSigningCredential() //添加登录证书
  5. .AddInMemoryIdentityResources(Config.GetIdentityResources()) //添加IdentityResources
  6. .AddInMemoryApiResources(Config.GetApiResources())
  7. .AddInMemoryClients(Config.GetClients())
  8. .AddTestUsers(Config.GeTestUsers());
  9. services.AddScoped<ConsentService>();
  10. services.AddMvc();
  11. }
  12. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  13. {
  14. if (env.IsDevelopment())
  15. {
  16. app.UseDeveloperExceptionPage();
  17. }
  18. else
  19. {
  20. app.UseExceptionHandler("/Home/Error");
  21. }
  22. app.UseStaticFiles();
  23. app.UseIdentityServer();//引用IdentityServer中间件
  24. app.UseMvc(routes =>
  25. {
  26. routes.MapRoute(
  27. name: "default",
  28. template: "{controller=Home}/{action=Index}/{id?}");
  29. });
  30. }

Startup配置IdentityServer

  • 添加一个ConsentService,用来根据Store拿到Resource
    1. public class ConsentService
    2. {
    3. private readonly IClientStore _clientStore;
    4. private readonly IResourceStore _resourceStore;
    5. private readonly IIdentityServerInteractionService _identityServerInteractionService;
    6.  
    7. public ConsentService(IClientStore clientStore,
    8. IResourceStore resourceStore,
    9. IIdentityServerInteractionService identityServerInteractionService)
    10. {
    11. _clientStore = clientStore;
    12. _resourceStore = resourceStore;
    13. _identityServerInteractionService = identityServerInteractionService;
    14. }
    15.  
    16. public async Task<ConsentViewModel> BuildConsentViewModel(string returnUrl)
    17. {
    18. //根据return url 拿到ClientId 等信息
    19. var request = await _identityServerInteractionService.GetAuthorizationContextAsync(returnUrl);
    20. if (returnUrl == null)
    21. return null;
    22. var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
    23. var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);//根据请求的scope 拿到resources
    24.  
    25. return CreateConsentViewModel(request, client, resources);
    26. }
    27.  
    28. private ConsentViewModel CreateConsentViewModel(AuthorizationRequest request, Client client, Resources resources)
    29. {
    30. var vm = new ConsentViewModel();
    31. vm.ClientName = client.ClientName;
    32. vm.ClientLoggoUrl = client.LogoUri;
    33. vm.ClientUrl = client.ClientUri;
    34. vm.RemeberConsent = client.AllowRememberConsent;
    35.  
    36. vm.IdentityScopes = resources.IdentityResources.Select(i => CreateScopeViewModel(i));
    37. //api resource
    38. vm.ResourceScopes = resources.ApiResources.SelectMany(i => i.Scopes).Select(x => CreateScopeViewModel(scope: x));
    39. return vm;
    40. }
    41. //identity 1个scopes
    42. private ScopeViewModel CreateScopeViewModel(IdentityResource identityResource)
    43. {
    44. return new ScopeViewModel
    45. {
    46. Name = identityResource.Name,
    47. DisplayName = identityResource.DisplayName,
    48. Description = identityResource.Description,
    49. Required = identityResource.Required,
    50. Checked = identityResource.Required,
    51. Emphasize = identityResource.Emphasize
    52. };
    53. }
    54. //apiresource
    55. private ScopeViewModel CreateScopeViewModel(Scope scope)
    56. {
    57. return new ScopeViewModel
    58. {
    59. Name = scope.Name,
    60. DisplayName = scope.DisplayName,
    61. Description = scope.Description,
    62. Required = scope.Required,
    63. Checked = scope.Required,
    64. Emphasize = scope.Emphasize
    65. };
    66. }
    67. }

    ConsentService

  • 添加一个ConsentController,用来显示授权登录页面,以及相应的跳转登录逻辑。
  1. public class ConsentController : Controller
  2. {
  3. private readonly ConsentService _consentService;
  4. public ConsentController(ConsentService consentService)
  5. {
  6. _consentService = consentService;
  7. }
  8.  
  9. public async Task<IActionResult> Index(string returnUrl)
  10. {
  11. //调用consentService的BuildConsentViewModelAsync方法,将跳转Url作为参数传入,解析得到一个ConsentViewModel
  12. var model =await _consentService.BuildConsentViewModelAsync(returnUrl);
  13. if (model == null)
  14. return null;
  15. return View(model);
  16. }
  17. [HttpPost]
  18. public async Task<IActionResult> Index(InputConsentViewModel viewModel)
  19. {
  20. //用户选择确认按钮的时候,根据选择按钮确认/取消,以及勾选权限
  21. var result = await _consentService.PorcessConsent(viewModel);
  22. if (result.IsRedirectUrl)
  23. {
  24. return Redirect(result.RedirectUrl);
  25. }
  26. if (!string.IsNullOrEmpty(result.ValidationError))
  27. {
  28. ModelState.AddModelError("", result.ValidationError);
  29. }
  30. return View(result.ViewModel);
  31. }
  32. }

ConsentController

  • 配置服务端的登录Controller
    1. public class AccountController : Controller
    2. {
    3. private readonly TestUserStore _user; //放入DI容器中的TestUser(GeTestUsers方法),通过这个对象获取
    4. public AccountController(TestUserStore user)
    5. {
    6. _user = user;
    7. } public IActionResult Login(string returnUrl = null)
    8. {
    9. ViewData["ReturnUrl"] = returnUrl;
    10. return View();
    11. }
    12.  
    13. [HttpPost]
    14. public async Task<IActionResult> Login(LoginViewModel loginViewModel,string returnUrl)
    15. {
    16. //用户登录
    17. if (ModelState.IsValid)
    18. {
    19. ViewData["ReturnUrl"] = returnUrl;
    20. var user = _user.FindByUsername(loginViewModel.Email);
    21. if (user == null)
    22. {
    23. ModelState.AddModelError(nameof(loginViewModel.Email), "Email not exists");
    24. }
    25. else
    26. {
    27. var result = _user.ValidateCredentials(loginViewModel.Email, loginViewModel.Password);
    28. if(result)
    29. {
    30. var props = new AuthenticationProperties()
    31. {
    32. IsPersistent = true,
    33. ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes()
    34. };
    35. await Microsoft.AspNetCore.Http.AuthenticationManagerExtensions.SignInAsync( //Id4扩展方法和HttpContext扩展方法重名,这里强制使用命名空间方法
    36. this.HttpContext,
    37. user.SubjectId,
    38. user.Username,
    39. props);
    40. return RedirectToLoacl(returnUrl);
    41. }
    42. else
    43. {
    44. ModelState.AddModelError(nameof(loginViewModel.Email), "Wrong password");
    45. }
    46. }
    47. }
    48.  
    49. return View();
    50. }

    AccountController

  • 接下来给Consent控制器的Index添加视图
    1. @using mvcCookieAuthSample.ViewModels
    2. @model ConsentViewModel
    3. <h2>ConsentPage</h2>
    4. @*consent*@
    5. <div class="row page-header">
    6. <div class="col-sm-10">
    7. @if (!string.IsNullOrWhiteSpace(Model.ClientLogoUrl))
    8. {
    9. <div>
    10. <img src="@Model.ClientLogoUrl" style="width:50px;height:50px" />
    11. </div>
    12. }
    13. <h1>@Model.ClientName</h1>
    14. <p>希望使用你的账户</p>
    15. </div>
    16. </div>
    17. @*客户端*@
    18. <div class="row">
    19. <div class="col-sm-8">
    20. <div asp-validation-summary="All" class="danger"></div>
    21. <form asp-action="Index" method="post">
    22. <input type="hidden" asp-for="ReturnUrl"/>
    23. @if (Model.IdentityScopes.Any())
    24. {
    25. <div class="panel">
    26. <div class="panel-heading">
    27. <span class="glyphicon glyphicon-user"></span>
    28. 用户信息
    29. </div>
    30. <ul class="list-group">
    31. @foreach (var scope in Model.IdentityScopes)
    32. {
    33. @Html.Partial("_ScopeListitem.cshtml", scope);
    34. }
    35. </ul>
    36. </div>
    37. }
    38. @if (Model.ResourceScopes.Any())
    39. {
    40. <ul class="list-group">
    41. @foreach (var scope in Model.ResourceScopes)
    42. {
    43. @Html.Partial("_ScopeListitem.cshtml", scope);
    44. }</ul>
    45. }
    46. <div>
    47. <label>
    48. <input type="checkbox" asp-for="RemeberConsent"/>
    49. <strong>记住我的选择</strong>
    50. </label>
    51. </div>
    52. <div>
    53. <button name="button" value="yes" class="btn btn-primary" autofocus>同意</button>
    54. <button name="button" value="no">取消</button>
    55. @if (!string.IsNullOrEmpty(Model.ClientUrl))
    56. {
    57. <a href="@Model.ClientUrl" class="pull-right btn btn-default">
    58. <span class="glyphicon glyphicon-info-sign" ></span>
    59. <strong>@Model.ClientUrl</strong>
    60. </a>
    61. }
    62. </div>
    63. </form>
    64. </div>
    65. </div>
    66. //这里用到了一个分部视图用来显示用户允许授权的身份资源和api资源
    67. @using mvcCookieAuthSample.ViewModels
    68. @model ScopeViewModel;
    69. <li>
    70. <label>
    71. <input type="checkbox"
    72. name="ScopesConsented"
    73. id="scopes_@Model.Name"
    74. value="@Model.Name"
    75. checked=@Model.Checked
    76. disabled=@Model.Required/>
    77. @if (Model.Required)
    78. {
    79. <input type="hidden" name="ScopesConsented" value="@Model.Name" />
    80. }
    81. <strong>@Model.Name</strong>
    82. @if (Model.Emphasize)
    83. {
    84. <span class="glyphicon glyphicon-exclamation-sign"></span>
    85. }
    86. </label>
    87. @if(string.IsNullOrEmpty(Model.Description))
    88. {
    89. <div>
    90. <label for="scopes_@Model.Name">@Model.Description</label>
    91. </div>
    92. }
    93. </li>

    Index.cshtml

  • 添加客户端,依旧添加一个mvc项目,配置startup,Home/Index action打上Authorize标签。
    1. public void ConfigureServices(IServiceCollection services)
    2. {
    3. services.AddAuthentication(options => {
    4. options.DefaultScheme = "Cookies";
    5. options.DefaultChallengeScheme = "oidc";//openidconnectservice
    6. })
    7. .AddCookie("Cookies")
    8. .AddOpenIdConnect("oidc",options=> {
    9. options.SignInScheme = "Cookies";
    10. options.Authority = "http://localhost:5000"; //设置认证服务器
    11. options.RequireHttpsMetadata = false;
    12. options.ClientId = "mvc"; //openidconfig的配置信息
    13. options.ClientSecret = "secret";
    14. options.SaveTokens = true;
    15. });
    16. services.AddMvc();
    17. }
    18. public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    19. {
    20. if (env.IsDevelopment())
    21. {
    22. app.UseDeveloperExceptionPage();
    23. app.UseBrowserLink();
    24. }
    25. else
    26. {
    27. app.UseExceptionHandler("/Home/Error");
    28. }
    29. app.UseStaticFiles();
    30. app.UseAuthentication();
    31. app.UseMvc(routes =>
    32. {
    33. routes.MapRoute(
    34. name: "default",
    35. template: "{controller=Home}/{action=Index}/{id?}");
    36. });
    37. }

    客户端配置Startup

设置服务端端口5000,运行服务器端;设置客户端端口5001,运行客户端。我们可以看到,localhost:5001会跳转至认证服务器

然后看下Url=》

使用config配置的testuser登录系统,选择允许授权的身份权限。登录成功后看到我们的Claims

总结

  • 最后来总结一下
    用户访问客户端(5001端口程序),客户端将用户导向认证服务器(5000程序),用户选择允许授权的身份资源和api资源后台解析(这两个资源分别由Resources提供,resources 由IResourceStore解析returnurl后的Scopes提供),最后由ProfileService返回数条Claim。(查看ConsentService的各个方法)

Core篇——初探IdentityServer4(OpenID Connect模式)的更多相关文章

  1. Core篇——初探IdentityServer4(客户端模式,密码模式)

    Core篇——初探IdentityServer4(客户端模式,密码模式) 目录 1.Oatuth2协议的客户端模式介绍2.IdentityServer4客户端模式实现3.Oatuth2协议的密码模式介 ...

  2. Core篇——初探Core配置管理

    文章目录 1.命令行配置 2.Json文件配置 3.配置文件文本至C#对象实例的映射 4.配置文件热更新 5.总结 命令行的配置 我们首先来创建一个.net core 的控制台项目,然后引入.net ...

  3. Core篇——初探Core的认证,授权机制

    目录 1.Cookie-based认证的实现 2.Jwt Token 的认证与授权 3.Identity Authentication + EF 的认证 Cookie-based认证的实现 cooki ...

  4. Core篇——初探Core的Http请求管道&&Middleware

    目录: 1.Core 处理HTTP请求流程 2.中间件(Middleware)&&处理流程 3.创建自定义中间件&&模拟Core的请求管道 Core 处理HTTP请求流 ...

  5. Core篇——初探依赖注入

    目录 1.DI&&IOC简单介绍 2.UML类图中六种关联关系 3..net core 中DI的使用 4..net core DI初始化源码初窥 DI&&IOC简单介绍 ...

  6. 使用 IdentityServer4 实现 OAuth 2.0 与 OpenID Connect 服务

    IdentityServer4 是 ASP.NET Core 的一个包含 OIDC 和 OAuth 2.0 协议的框架.最近的关注点在 ABP 上,默认 ABP 也集成 IdentityServer4 ...

  7. asp.net core系列 53 IdentityServer4 (IS4)介绍

    一.概述 在物理层之间相互通信必须保护资源,需要实现身份验证和授权,通常针对同一个用户存储.对于资源安全设计包括二个部分,一个是认证,一个是API访问. 1 认证 认证是指:应用程序需要知道当前用户的 ...

  8. IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习保护API

    IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习之保护API. 使用IdentityServer4 来实现使用客户端凭据保护ASP.N ...

  9. ASP.NET Core的身份认证框架IdentityServer4(9)-使用OpenID Connect添加用户认证

    OpenID Connect OpenID Connect 1.0是OAuth 2.0协议之上的一个简单的身份层. 它允许客户端基于授权服务器执行的身份验证来验证最终用户的身份,以及以可互操作和类似R ...

随机推荐

  1. Python对JSON的操作 day3

    下面将为大家介绍如何使用python语言来编码和解码json对象: json串就是一个字符串,json串必须用双引号,不能使用单引号 使用json函数需要导入json库,import json 1.j ...

  2. 移动端自动化测试-WTF Appium

    手机App分为两大类,原生App(Native App)和混合APP(Hybrid App) 原生App(Native App) 原生App实际就是我们所常见的传统App开发模式,云端数据存储+App ...

  3. selenium使用Xpath+CSS+JavaScript+jQuery的定位方法(治疗selenium各种定位不到,点击不了的并发症)

    跟你说,你总是靠那个firebug,chrome的F12啥的右击复制xpath绝对总有一天踩着地雷炸的你死活定位不到,这个时候就需要自己学会动手写xpath,人脑总比电脑聪明,开始把xpath语法给我 ...

  4. C#第三节课(1)

    数据类型 using System;using System.Collections.Generic;using System.Linq;using System.Text;using System. ...

  5. Node.js+Protractor+vscode搭建测试环境(1)

    1.protractor简介 官网地址:http://www.protractortest.org/ Protractor是一个end-to-end的测试框架,从网络上得到的答案是Protractor ...

  6. QBXT春季培训酱油记

    Day-1: 下午回家收拾东西,明天去JN,先通知一下在JN的lll dalao明天去见他,然而手办到了,心情大好啊有没有,有没有! 晚上单曲循环<初音未来的消失>,睡觉的时候哭得稀里哗啦 ...

  7. 利用pandas库中的read_html方法快速抓取网页中常见的表格型数据

    本文转载自:https://www.makcyun.top/web_scraping_withpython2.html 需要学习的地方: (1)read_html的用法 作用:快速获取在html中页面 ...

  8. Linux思维导图之进程管理

    查漏补缺,理解概念,及时总结,欢迎拍砖.

  9. VirtualBox没有权限访问共享文件夹

    将用户添加至vboxsf组 命令: sudo adduser ly vboxsf 搞定!

  10. hdu1542 Atlantis(扫描线+线段树+离散)矩形相交面积

    题目链接:点击打开链接 题目描写叙述:给定一些矩形,求这些矩形的总面积.假设有重叠.仅仅算一次 解题思路:扫描线+线段树+离散(代码从上往下扫描) 代码: #include<cstdio> ...