IdentityServer4揭祕---登陸

IdentityServer4默認提供了的登陸地址是Account/Index 贊成頁面是Consent/Indexhtml

這裏咱們能夠經過IdentittyServer4的用戶交互自定義配置設置瀏覽器

ConfigureServices服務中添加services.AddIdentityServer() 在參數中提供了UserInteraction設置async

 

services.AddIdentityServer(options => { options.UserInteraction = new IdentityServer4.Configuration.UserInteractionOptions { LoginUrl = "/Account/Login",//【必備】登陸地址 
                    LogoutUrl = "/Account/Logout",//【必備】退出地址 
                    ConsentUrl = "/Account/Consent",//【必備】容許受權贊成頁面地址
                    ErrorUrl = "/Account/Error", //【必備】錯誤頁面地址
                    LoginReturnUrlParameter = "ReturnUrl",//【必備】設置傳遞給登陸頁面的返回URL參數的名稱。默認爲returnUrl 
                    LogoutIdParameter = "logoutId", //【必備】設置傳遞給註銷頁面的註銷消息ID參數的名稱。缺省爲logoutId 
                    ConsentReturnUrlParameter = "ReturnUrl", //【必備】設置傳遞給贊成頁面的返回URL參數的名稱。默認爲returnUrl
                    ErrorIdParameter = "errorId", //【必備】設置傳遞給錯誤頁面的錯誤消息ID參數的名稱。缺省爲errorId
                    CustomRedirectReturnUrlParameter = "ReturnUrl", //【必備】設置從受權端點傳遞給自定義重定向的返回URL參數的名稱。默認爲returnUrl
 CookieMessageThreshold = 5 //【必備】因爲瀏覽器對Cookie的大小有限制,設置Cookies數量的限制,有效的保證了瀏覽器打開多個選項卡,一旦超出了Cookies限制就會清除之前的Cookies值
 }; }) .AddDeveloperSigningCredential() .AddInMemoryIdentityResources(MemoryClients.GetIdentityResources()) .AddInMemoryApiResources(MemoryClients.GetApiResources()) .AddInMemoryClients(MemoryClients.GetClients());
View Code

 

這裏我指定的都是在個人AccountController中,指定好了頁面,咱們來開始作咱們的登陸界面ide

登陸通常須要用戶名、密碼、記住密碼字段,可是在IdentityServer4中還提供了一個ReturnUrl,在Client端OIDC受權訪問的時候會轉接到IdenttityServer4服務端進行驗證而且構建好相關的ReturnUrl地址函數

ReturnUrl是一個很是重要的參數,它在整個受權過程當中充當了重要的做用oop

想到登陸界面,分析好了模型,接下來就是構建模型 首先構建 界面視圖模型:LoginViewModelpost

public class LoginViewModel { /// <summary>
         /// 用戶名 /// </summary>
 [Required] public string username { get; set; } /// <summary>
        /// 密碼 /// </summary>
 [Required] public string password { get; set; } /// <summary>
        /// 界面上的選擇框 選擇是否記住登陸 /// </summary>
        public bool RememberLogin { get; set; } /// <summary>
        /// 回調受權驗證地址 這個地址與Redirect地址不同 /// 登陸成功後會轉到 ReturnUrl 而後驗證受權登陸後 獲取到客戶端的信息 而後根據Client配置中的RedirectUrl轉到對應的系統 /// </summary>
        public string ReturnUrl { get; set; } }
LoginViewModel

登記界面會涉及到IdentityServer4相關交互,好比客戶端名稱ClientName 、ClientUrl等等測試

因此在登記界面咱們在構建一個與IdentityServer4相關的模型類去繼承LoginViewModel,由於他們是在同一個界面展示:Idr4LoginViewModelui

public class Idr4LoginViewModel : LoginViewModel { public bool AllowRememberLogin { get; set; } public bool EnableLocalLogin { get; set; } public IEnumerable<ExternalProvider> ExternalProviders { get; set; } //public IEnumerable<ExternalProvider> VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName));

        public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; public string ExternalLoginScheme => ExternalProviders?.SingleOrDefault()?.AuthenticationScheme; public string ClientName { get; set; } public string ClientUrl { get; set; } public string ClientLogoUrl { get; set; } }
View Code

接下來就是構建登陸頁面的html,這裏我構建的比較簡單沒有什麼樣式 測試下就好了spa

@using SSOServer.Models; @{ ViewData["Title"] = "Index"; } @model Idr4LoginViewModel <h2>用戶登陸</h2>
<form asp-action="Login"> @if (Model.EnableLocalLogin) { <div><img src="@Model.ClientLogoUrl" width="100" height="100" /></div>
        <div>@Model.ClientName</div>
        <div>@Model.ClientUrl</div> } <div>用戶名:<input type="text" asp-for="username" /></div>
    <div>密碼:<input type="text" asp-for="password" /></div>
    <input type="hidden" asp-for="ReturnUrl" />
    <button type="submit">登陸</button>
    <div asp-validation-summary="All"> </div>
</form>
HTML 登陸

這裏也能夠獲取Client的信息,均可以自定義按需求處理

在前面的UserInteraction中作了登陸界面的設置而且指定了參數ReturnUrl,因此到鏈接轉到視圖頁面時候,須要Get請求接受一個ReturnUrl的參數

 [HttpGet] public async Task<IActionResult> Login(string ReturnUrl) { //建立視圖模型
            var vm = await CreateIdr4LoginViewModelAsync(ReturnUrl); //判斷來之其餘客戶端的登陸
            if (vm.IsExternalLoginOnly) { return await ExternalLogin(vm.ExternalLoginScheme, ReturnUrl); } return View(vm); }
Get LoginView

那麼登陸界面怎麼來作來,這裏就須要介紹IdentityServer4中的幾個接口類了:

IIdentityServerInteractionService:用戶交互相關接口

IResourceStore:獲取資源接口:這裏包括2中資源 一種是IdentityResource 和 ApiResource

IClientStore:獲取客戶端相關接口

IEventService:事件服務

UserStoreServices:自定義的用戶服務,這裏我沒有用IdentityServer4的TestUserStore是爲了方面自定義處理

轉到登陸視圖頁面,首先要作的就是構建視圖模型,頁面上要展現什麼數據,包括用戶名,密碼,Idr4相關

這個時候就是ReturnUrl發揮其重要性的時候了:

DotNetCore自帶的有DependencyInjection這樣的依賴注入,能夠不用Autofac之類也很是方便

在AccountController中注入相關接口

private readonly IIdentityServerInteractionService _identityServerInteractionService; private readonly IHttpContextAccessor _httpContextAccessor; private readonly IAuthenticationSchemeProvider _schemeProvider; private readonly IResourceStore _resourceStore; private readonly IClientStore _clientStore; private readonly IEventService _events; private readonly UserStoreServices _testUserStore; //private readonly TestUserStore _testUserStore;
        public AccountController(IIdentityServerInteractionService identityServerInteractionService, UserStoreServices testUserStore, IEventService events, IHttpContextAccessor httpContextAccessor, IAuthenticationSchemeProvider schemeProvider, IClientStore clientStore, IResourceStore resourceStore) { _identityServerInteractionService = identityServerInteractionService; _testUserStore = testUserStore; _events = events; _httpContextAccessor = httpContextAccessor; _schemeProvider = schemeProvider; _clientStore = clientStore; _resourceStore = resourceStore; }
接口及構造

這裏調用用戶交互接口以及客戶端接口構建以下

/// <summary>
        /// 構造下Idr4登錄界面顯示視圖模型 /// </summary>
        /// <param name="ReturnUrl"></param>
        /// <returns></returns>
        private async Task<Idr4LoginViewModel> CreateIdr4LoginViewModelAsync(string ReturnUrl) { Idr4LoginViewModel vm = new Idr4LoginViewModel(); var context = await _identityServerInteractionService.GetAuthorizationContextAsync(ReturnUrl); if (context != null) { if (context?.IdP != null) { // 擴展外部擴展登陸模型處理
                    vm.EnableLocalLogin = false; vm.ReturnUrl = ReturnUrl; vm.username = context?.LoginHint; vm.ExternalProviders = new ExternalProvider[] { new ExternalProvider { AuthenticationScheme = context.IdP } }; } } //外部登錄 獲取全部受權信息 並查找當前可用的受權信息
            var schemes = await _schemeProvider.GetAllSchemesAsync(); var providers = schemes .Where(x => x.DisplayName != null) .Select(x => new ExternalProvider { DisplayName = x.DisplayName, AuthenticationScheme = x.Name }).ToList(); var allowLocal = true; if (context?.ClientId != null) { var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId); if (client != null) { allowLocal = client.EnableLocalLogin; vm.ClientName = client.ClientName; vm.ClientUrl = client.ClientUri; vm.ClientLogoUrl = client.LogoUri; if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) { providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); } } } vm.AllowRememberLogin = AccountOptions.AllowRememberLogin; vm.EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin; vm.ReturnUrl = ReturnUrl; vm.username = context?.LoginHint; vm.ExternalProviders = providers.ToArray(); return vm; }
模型

IIdentityServerInteractionService 用戶交互下提供了不少接口方法,能夠詳細瞭解下

對應代碼中的擴展登陸能夠註釋掉 目前不作那塊相關

到了這裏基本能夠展現代碼了,下面運行下代碼看下:

本生的客戶端系統我寄宿到5001端口,IdentityServer4寄宿到5000端口,訪問5000中受權限制訪問頁面,會轉到Idr4 服務端

這裏咱們能夠看到ReturnUrl,分析下這個地址:

http://localhost:5000/Account/Login?ReturnUrl=%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3Dliyouming%26redirect_uri%3Dhttp%253A%252F%252Flocalhost%253A5001%252Fsignin-oidc%26response_type%3Dcode%2520id_token%26scope%3Dopenid%2520profile%26response_mode%3Dform_post%26nonce%3D636592109017726544.MGI1MDJkNDYtMmUwOS00YmUxLWJmODgtODY0NWZlYzQyZGEyMjY1MGExMTItNjc3Yi00M2ExLWJhNmItZWM0OWRlYTEwOWQx%26state%3DCfDJ8GJf-n3goONOsPJOurEXDE-aBinqSDzf_TJntjbg5FIJpAFEeJm36TR7MxDhYJB_K3yzkedqbCi1P2V_F4dJ5wrOEbvhkVBJr447GQCdJKoFV1Ms2POKRn-_kB03Xp4ydGttsBUDJflnaLYcC3BnN7UTAcHV55ALZBTgGTNTGPnzIhotUonX9IM6SgOTaNZTmlwrIRz6s-XksqJQ5-gsnLXh_MRqcKAxzC3-HLIc34re2H6cTnJT1CNab0B7MxJGUpeOZ09_x7U7gw9DnF0aMvAae9-_dTPDgo2xEbMw9y5hLaFwIPfMbrftrHJoFI87tF-TmHHKm9NvJfLfueWZ02o%26x-client-SKU%3DID_NET%26x-client-ver%3D2.1.4.0

這裏面有受權回調地址,就是登陸成功後會Post到 受權callback地址進行認證,成功後會轉到redirect_uri,這裏面還指定了 請求的scope ,repsonsetype等等,能夠看下oauth2相關資料

當登陸的時候咱們須要一個Post的登陸Action,這裏注意的是 這個ReturnUrl 會貫穿這個登陸流程,因此在登陸視圖界面會有一個隱藏域把這個存起來,在post請求的時候要帶過來

 [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(Idr4LoginViewModel model) { #region  Idr4驗證處理 這裏主要對ReturnUrl處理

            var context = await _identityServerInteractionService.GetAuthorizationContextAsync(model.ReturnUrl); if (context == null) { //不存在客戶端信息
                Redirect("~/"); } #endregion
            #region 基礎驗證
            if (string.IsNullOrEmpty(model.username)) { ModelState.AddModelError("", "請輸入用戶名"); } if (string.IsNullOrEmpty(model.password)) { ModelState.AddModelError("", "請輸入密碼"); } #endregion
            if (ModelState.IsValid) { if (_testUserStore.ValidatorUser(model.username, model.password)) { //查詢用戶信息
                    var user = await _testUserStore.GetByUserNameAsync(); //獲得信息
                    await _events.RaiseAsync(new UserLoginSuccessEvent(user.username, user.guid.ToString(), user.username)); //記住登陸
                    AuthenticationProperties authenticationProperties = null; if (AccountOptions.AllowRememberLogin && model.RememberLogin) { authenticationProperties = new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) }; } //SignIn
                    await HttpContext.SignInAsync(user.guid.ToString(), user.username, authenticationProperties); if (_identityServerInteractionService.IsValidReturnUrl(model.ReturnUrl) || Url.IsLocalUrl(model.ReturnUrl)) { return Redirect(model.ReturnUrl); } return Redirect("~/"); } else { await _events.RaiseAsync(new UserLoginFailureEvent(model.username, "登陸失敗")); ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage); } } //防止驗證失敗後返回視圖後 界面模型參數不存在 因此這裏須要構建一次模型
            var vm = await CreateIdr4LoginViewModelAsync(model.ReturnUrl); return View(vm); }
Post Login

post裏面就能夠作一些處理就好了好比驗證之類,驗證失敗或者處理失敗都要回到登陸頁面上,因此最後仍是須要構建一次視圖模型返回到View上

到這裏登陸基本就結束了

在擴充一點內存配置

public class MemoryClients { public static List<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResource{ Name="openid", Enabled=true, Emphasize=true, Required=true, DisplayName="用戶受權認證信息", Description="獲取你的受權認證" }, new IdentityResource{ Name="profile", Enabled=true, Emphasize=false, Required=true, DisplayName="用戶我的信息", Description="獲取你的我的基本資料信息,如:姓名、性別、年齡等" } }; } public static List<ApiResource> GetApiResources() { return new List<ApiResource> { //普通的經過構造函數限制 指定scope以及displayname 就好了 // new ApiResource("liyouming","打印雲服務接口") //作一些更加嚴格的限制要求
               new ApiResource(){ Enabled=true, Name="liyouming", DisplayName="打印雲服務接口", Description="選擇容許即贊成獲取你的我的打印服務權限", Scopes={ new Scope() { Emphasize=false, Required=false, Name="liyouming", DisplayName="打印雲服務接口", Description="選擇容許即贊成獲取你的我的打印服務權限" } } } }; } public static List<Client> GetClients() { return new List<Client> { new Client(){ ClientId="liyouming", ClientName="ChinaNetCore", ClientUri="http://www.chinanetcore.com", LogoUri="http://img05.tooopen.com/images/20160109/tooopen_sy_153858412946.jpg", ClientSecrets={new Secret("liyouming".Sha256()) }, AllowedGrantTypes= GrantTypes.Hybrid, AccessTokenType= AccessTokenType.Jwt, RequireConsent=true, RedirectUris={ "http://localhost:5001/signin-oidc" }, PostLogoutRedirectUris={"http://localhost:5001/signout-callback-oidc" }, AllowedScopes={ "openid", "profile", "liyouming", }, BackChannelLogoutUri="http://localhost:5001/Default/LogoutByElse", BackChannelLogoutSessionRequired=true } }; } }
Client Resource 配置

其餘站點請求受權可OIDC配置,在DotNetCore中自帶了OpenIdConnect

services.AddAuthentication(option => { option.DefaultScheme = "Cookies"; option.DefaultChallengeScheme = "oidc"; }) .AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.ClientId = "liyouming"; options.ClientSecret = "liyouming"; options.SignedOutRedirectUri = "http://localhost:5001/signout-callback-oidc"; options.SaveTokens = false; options.Events = new Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectEvents { OnRedirectToIdentityProviderForSignOut= OnRedirectToIdentityProviderForSignOut }; });
代碼

這裏配置好並添加好相關Controller的受權訪問後便可

登陸失敗後提示

登陸成功後

這裏來到了Conset受權贊成頁面,這裏我在後面繼續講解

贊成後進入受權訪問頁面

 

 登陸到這裏就結束了,後面會繼續介紹 Consent  及 Logout等操做和其餘一些DotNetCore相關實戰運用  

相關文章
相關標籤/搜索