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


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 = "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
  • 配置服務端的登錄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(30)
    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的各個方法)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM