Core篇——初探IdentityServer4(OpenID Connect模式)

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

目录

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

Oauth2协议授权码模式介绍

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

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 = "1",
    48                     Username = "alice",
    49                     Password = "password"
    50                 },
    51                 new TestUser
    52                 {
    53                     SubjectId = "2",
    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 
       8         public ConsentService(IClientStore clientStore,
       9             IResourceStore resourceStore,
      10             IIdentityServerInteractionService identityServerInteractionService)
      11         {
      12             _clientStore = clientStore;
      13             _resourceStore = resourceStore;
      14             _identityServerInteractionService = identityServerInteractionService;
      15         }
      16 
      17         public async Task<ConsentViewModel> BuildConsentViewModel(string returnUrl)
      18         {
      19             //根据return url 拿到ClientId 等信息
      20             var request = await _identityServerInteractionService.GetAuthorizationContextAsync(returnUrl);
      21             if (returnUrl == null)
      22                 return null;
      23             var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
      24             var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);//根据请求的scope 拿到resources
      25 
      26 
      27             return CreateConsentViewModel(request, client, resources);
      28         }
      29 
      30         private ConsentViewModel CreateConsentViewModel(AuthorizationRequest request, Client client, Resources resources)
      31         {
      32             var vm = new ConsentViewModel();
      33             vm.ClientName = client.ClientName;
      34             vm.ClientLoggoUrl = client.LogoUri;
      35             vm.ClientUrl = client.ClientUri;
      36             vm.RemeberConsent = client.AllowRememberConsent;
      37 
      38             vm.IdentityScopes = resources.IdentityResources.Select(i => CreateScopeViewModel(i));
      39             //api resource
      40             vm.ResourceScopes = resources.ApiResources.SelectMany(i => i.Scopes).Select(x => CreateScopeViewModel(scope: x));
      41             return vm;
      42         }
      43         //identity 1个scopes
      44         private ScopeViewModel CreateScopeViewModel(IdentityResource identityResource)
      45         {
      46             return new ScopeViewModel
      47             {
      48                 Name = identityResource.Name,
      49                 DisplayName = identityResource.DisplayName,
      50                 Description = identityResource.Description,
      51                 Required = identityResource.Required,
      52                 Checked = identityResource.Required,
      53                 Emphasize = identityResource.Emphasize
      54             };
      55         }
      56         //apiresource
      57         private ScopeViewModel CreateScopeViewModel(Scope scope)
      58         {
      59             return new ScopeViewModel
      60             {
      61                 Name = scope.Name,
      62                 DisplayName = scope.DisplayName,
      63                 Description = scope.Description,
      64                 Required = scope.Required,
      65                 Checked = scope.Required,
      66                 Emphasize = scope.Emphasize
      67             };
      68         }
      69     }
      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
  • 接下来给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="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
  • 设置服务端端口5000,运行服务器端;设置客户端端口5001,运行客户端。我们可以看到,localhost:5001会跳转至认证服务器

    Core篇——初探IdentityServer4(OpenID Connect模式)

    然后看下Url=》

    Core篇——初探IdentityServer4(OpenID Connect模式)

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

     

    Core篇——初探IdentityServer4(OpenID Connect模式)Core篇——初探IdentityServer4(OpenID Connect模式)

    总结

    相关推荐