1、Startuphtml
3、定義Clientsnode
4、登陸git
5、使用外部身份提供商登陸github
7、登出數據庫
8、註銷外部身份提供商npm
9、聯合註銷編程
10、聯合網關json
十6、Cryptography, Keys and HTTPS
二10、Resource Owner Password Validation
IdentityServer是中間件和服務的組合。 全部的配置都在你的startup類上完成。
配置服務
經過調用如下方式將IdentityServer服務添加到DI系統:
public void ConfigureServices(IServiceCollection services) { var builder = services.AddIdentityServer(); }
或者,您能夠將選項傳入此調用。
這將返回一個構建器對象,該構造器對象又有許多方便的方法來鏈接其餘服務。
key材料
AddSigningCredential
AddDeveloperSigningCredential
AddValidationKey
In-Memory configuration stores
各類「in-memory」從內存中配置IdentityServer。這些「in-memory」集合能夠在宿主應用程序中進行硬編碼,也能夠從配置文件或數據庫動態加載。 可是,經過設計,這些集合僅在託管應用程序啓動時建立。
使用這些配置API可用於原型設計,開發和或測試時不須要在運行時動態查詢配置數據的數據庫。 若是配置不多發生變化,這種配置方式也適用於生產方案,或者若是必須更改值,則須要從新啓動應用程序並不方便。
AddInMemoryClients
AddInMemoryIdentityResources
AddInMemoryApiResources
Test stores
TestUser類在IdentityServer中爲用戶及其憑據和聲明建模。TestUser的使用與使用「in-memory」存儲相似,由於它適用於原型開發和/或測試。 生產中不推薦使用TestUser。
AddTestUsers
基於TestUser對象的集合註冊TestUserStore。 TestUserStore由默認的快速入門用戶界面使用。 還註冊IProfileService和IResourceOwnerPasswordValidator的實現。
其餘服務
AddExtensionGrantValidator
AddSecretParser
AddSecretValidator
AddResourceOwnerValidator
AddProfileService
AddAuthorizeInteractionResponseGenerator
AddCustomAuthorizeRequestValidator
AddCustomTokenRequestValidator
AddRedirectUriValidator
AddAppAuthRedirectUriValidator
AddJwtBearerClientAuthentication
Caching
客戶端和資源配置數據常常被IdentityServer使用。 若是從數據庫或其餘外部存儲裝載此數據,則常常從新加載相同數據可能會很昂貴。
AddInMemoryCaching
AddClientStoreCache
AddResourceStoreCache
AddCorsPolicyCache
進一步定製緩存是可能的:
默認緩存依賴於ICache <T>實現。 若是您但願爲特定配置對象定製緩存行爲,則能夠在依賴注入系統中替換此實現。
Cache <T>自己的默認實現依賴於.NET提供的IMemoryCache接口(和MemoryCache實現)。 若是您但願自定義內存中緩存行爲,則能夠替換依賴注入系統中的IMemoryCache實現。
配置管道
您須要經過調用如下方式將IdentityServer添加到管道中:
public void Configure(IApplicationBuilder app) { app.UseIdentityServer(); }
UseIdentityServer包含對UseAuthentication的調用,因此沒有必要同時擁有二者。
沒有額外的中間件配置。
請注意,管道的順序中很重要。 例如,您須要在實現登陸屏幕的UI框架以前添加IdentitySever。
您一般在系統中定義的第一件事是您要保護的資源。 這多是用戶的身份信息,例如我的資料數據或電子郵件地址,或訪問API。
定義identity resources
identity resources是數據,如用戶ID,姓名或用戶的電子郵件地址。 身份資源具備惟一的名稱,您能夠爲其分配任意聲明類型。 這些聲明將包含在用戶的身份標識中。 客戶端將使用scope參數來請求訪問身份資源。
OpenID Connect規範指定了幾個標準身份資源。 最低要求是,您提供了爲用戶發佈惟一ID的支持 - 也稱爲subject id。 這是經過暴露名爲openid的標準身份資源完成的:
public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId() }; }
IdentityResources類支持規範中定義的全部範圍(openid,電子郵件,配置文件,電話和地址)。 若是您想所有支持它們,能夠將它們添加到支持的身份資源列表中:
public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Email(), new IdentityResources.Profile(), new IdentityResources.Phone(), new IdentityResources.Address() }; }
自定義identity resources
您還能夠自定義標識資源。 建立一個新的IdentityResource類,給它一個名稱和一個可選的顯示名稱和描述,並在請求此資源時定義哪些用戶聲明應該包含在身份令牌中:
public static IEnumerable<IdentityResource> GetIdentityResources() { var customProfile = new IdentityResource( name: "custom.profile", displayName: "Custom profile", claimTypes: new[] { "name", "email", "status" }); return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), customProfile }; }
定義API resources
爲了容許客戶端請求API的訪問令牌,您須要定義API資源,例如:
要獲取API的訪問令牌,還須要將它們註冊爲scope。 此次scope類型是Resource類型的:
public static IEnumerable<ApiResource> GetApis() { return new[] { // 簡單的API與單一scope(在這種狀況下,scope名稱與api名稱相同) new ApiResource("api1", "Some API 1"), // 若是須要更多的控制,擴展版本 new ApiResource { Name = "api2", // secret for using introspection endpoint ApiSecrets = { new Secret("secret".Sha256()) }, // 在訪問令牌中包含如下使用聲明(除了subject ID) UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.Email }, //此API定義了兩個scope Scopes = { new Scope() { Name = "api2.full_access", DisplayName = "Full access to API 2", }, new Scope { Name = "api2.read_only", DisplayName = "Read only access to API 2" } } } }; }
客戶端表明能夠從您的身份服務器請求令牌的應用程序。
細節有所不一樣,但您一般爲客戶端定義如下經常使用設置:
定義服務器到服務器通訊的客戶端
在這種狀況下,不存在交互用戶—服務(又稱客戶端)但願與API(又稱範圍)進行通訊:
public class Clients { public static IEnumerable<Client> Get() { return new List<Client> { new Client { ClientId = "service.client", ClientSecrets = { new Secret("secret".Sha256()) }, AllowedGrantTypes = GrantTypes.ClientCredentials, AllowedScopes = { "api1", "api2.read_only" } } }; } }
定義基於瀏覽器的JavaScript客戶端(例如SPA)以進行用戶身份驗證和委派訪問以及API
該客戶端使用所謂的隱式流來從JavaScript請求身份和訪問令牌:
var jsClient = new Client { ClientId = "js", ClientName = "JavaScript Client", ClientUri = "http://identityserver.io", AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, RedirectUris = { "http://localhost:7017/index.html" }, PostLogoutRedirectUris = { "http://localhost:7017/index.html" }, AllowedCorsOrigins = { "http://localhost:7017" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Email, "api1", "api2.read_only" } };
定義服務器端Web應用程序(例如MVC)以使用身份驗證和委派API訪問
交互式服務器端(或本地桌面/移動)應用程序使用混合流。 此流提供了最佳的安全性,由於訪問令牌僅經過後通道調用傳輸(並容許您訪問refresh令牌)::
var mvcClient = new Client { ClientId = "mvc", ClientName = "MVC Client", ClientUri = "http://identityserver.io", AllowedGrantTypes = GrantTypes.Hybrid, AllowOfflineAccess = true, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:21402/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:21402/" }, FrontChannelLogoutUri = "http://localhost:21402/signout-oidc", AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Email, "api1", "api2.read_only" }, };
爲了讓IdentityServer表明用戶發出令牌,該用戶必須登陸到IdentityServer。
Cookie身份驗證
使用來自ASP.NET Core的cookie身份驗證處理程序管理的cookie跟蹤身份驗證。
IdentityServer註冊兩個cookie處理程序(一個用於身份驗證會話,另外一個用於臨時的外部cookie)。 這些是默認使用的,若是要手動引用它們,能夠從IdentityServerConstants類(DefaultCookieAuthenticationScheme和ExternalCookieAuthenticationScheme)獲取它們的名稱。
咱們只公開這些cookie的基本設置(到期和滑動),若是您須要更多控制,您能夠註冊本身的cookie處理程序。 當使用ASP.NET Core中的AddAuthentication時,IdentityServer使用與AuthenticationOptions上配置的任何cookie處理程序匹配的DefaultAuthenticateScheme。
重寫cookie處理程序配置
若是您但願使用本身的cookie身份驗證處理程序,則必須本身配置它。 在DI(使用AddIdentityServer)註冊IdentityServer後,必須在ConfigureServices中完成此操做。 例如:
services.AddIdentityServer() .AddInMemoryClients(Clients.Get()) .AddInMemoryIdentityResources(Resources.GetIdentityResources()) .AddInMemoryApiResources(Resources.GetApiResources()) .AddDeveloperSigningCredential() .AddTestUsers(TestUsers.Users); services.AddAuthentication("MyCookie") .AddCookie("MyCookie", options => { options.ExpireTimeSpan = ...; });
IdentityServer內部使用自定義方案(經過常量IdentityServerConstants.DefaultCookieAuthenticationScheme)調用AddAuthentication和AddCookie,所以要覆蓋它們,必須在AddIdentityServer以後進行相同的調用。
登陸用戶界面和身份管理系統
IdentityServer不爲用戶認證提供任何用戶界面或用戶數據庫。 這些是您但願本身提供或開發的東西。
若是您須要基本UI的起點(登陸,註銷,贊成和管理受權),您可使用咱們的quickstart UI.
快速啓動用戶界面根據內存數據庫對用戶進行身份驗證。 您能夠經過訪問真實用戶存儲來替換這些位。 咱們有使用ASP.NET Identity的示例。
登陸工做流程
當IdentityServer在受權端點收到請求而且用戶未經過身份驗證時,用戶將被重定向到配置的登陸頁面。 您必須經過選項上的UserInteraction設置(默認爲/ account/login)來通知IdentityServer您的登陸頁面的路徑。 將傳遞returnUrl參數,通知您的登陸頁面,一旦登陸完成,應該重定向用戶。
services.AddIdentityServer(options=>{ options.UserInteraction.LoginUrl="/Identity/Account/Login"; }) .AddDeveloperSigningCredential() .AddInMemoryPersistedGrants() .AddInMemoryApiResources(Config.GetApiResource()) .AddInMemoryIdentityResources(Config.GetIdentityResource()) .AddInMemoryClients(Config.GetClient()) .AddAspNetIdentity<IdentityUser>();
登陸上下文
在您的登陸頁面上,您可能須要有關請求上下文的信息,以便自定義登陸體驗(如客戶端,提示參數,IdP提示或其餘內容)。 這能夠經過交互服務上的GetAuthorizationContextAsync API獲取。
發佈cookie和聲明
在ASP.NET Core的HttpContext上有與身份驗證相關的擴展方法來發布身份驗證cookie並簽署用戶身份。所使用的身份驗證方案必須與您正在使用的cookie處理程序匹配(請參閱上文)。
當您在用戶中籤名時,您必須發出至少一個子claim和一個名稱claim。IdentityServer還在HttpContext上提供一些SignInAsync擴展方法,使其更加方便。
您還能夠選擇發出idp聲明(用於身份提供者名稱),amr聲明(用於所使用的身份驗證方法)和/或auth_time聲明(用於用戶驗證的紀元時間)。 若是您不提供這些,IdentityServer將提供默認值。
ASP.NET Core具備處理外部認證的靈活方式。 這涉及幾個步驟。
爲外部提供者添加認證處理程序
與外部提供者通訊所需的協議實現封裝在身份驗證處理程序中。 一些提供商使用專有協議(例如Facebook等社交提供商),有些提供商使用標準協議,例如 OpenID Connect,WS-Federation或SAML2p。
Cookie的做用
外部認證處理程序上的一個選項稱爲SignInScheme,例如:
services.AddAuthentication() .AddGoogle("Google", options => { options.SignInScheme = "scheme of cookie handler to use"; options.ClientId = "..."; options.ClientSecret = "..."; })
登陸方案指定將臨時存儲外部認證結果的cookie處理程序的名稱,例如 這獲得了由外部供應商發來的聲明。 這是必要的,由於在完成外部認證過程以前,一般會有幾個重定向。
鑑於這是一種常見作法,IdentityServer專門爲此外部提供程序工做流程註冊一個Cookie處理程序。 該方案經過IdentityServerConstants.ExternalCookieAuthenticationScheme常量表示。 若是您要使用咱們的外部cookie處理程序,那麼對於上面的SignInScheme,您將分配的值爲IdentityServerConstants.ExternalCookieAuthenticationScheme常量:
services.AddAuthentication() .AddGoogle("Google", options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.ClientId = "..."; options.ClientSecret = "..."; })
您也能夠註冊本身的自定義cookie處理程序,以下所示:
services.AddAuthentication() .AddCookie("YourCustomScheme") .AddGoogle("Google", options => { options.SignInScheme = "YourCustomScheme"; options.ClientId = "..."; options.ClientSecret = "..."; })
觸發認證處理程序
您能夠經過HttpContext上的ChallengeAsync擴展方法(或使用MVC ChallengeResult)調用外部認證處理程序。
您一般但願將一些選項傳遞給challenge操做,例如回調頁面的路徑和簿記提供程序的名稱,例如:
var callbackUrl = Url.Action("ExternalLoginCallback"); var props = new AuthenticationProperties { RedirectUri = callbackUrl, Items = { { "scheme", provider }, { "returnUrl", returnUrl } } }; return Challenge(provider, props);
處理回調並在用戶中籤名
在回調頁面上,您的典型任務是:
檢查外部身份:
// 從臨時cookie中讀取外部身份 var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); if (result?.Succeeded != true) { throw new Exception("External authentication error"); } //檢索外部用戶的聲明 var externalUser = result.Principal; if (externalUser == null) { throw new Exception("External authentication error"); } // 檢索外部用戶的聲明 var claims = externalUser.Claims.ToList(); // 嘗試肯定外部用戶的惟一ID - 最多見的聲明類型是子聲明和NameIdentifier // 根據外部提供商,可能會使用其餘一些聲明類型 var userIdClaim = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject); if (userIdClaim == null) { userIdClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier); } if (userIdClaim == null) { throw new Exception("Unknown userid"); } var externalUserId = userIdClaim.Value; var externalProvider = userIdClaim.Issuer; // 使用externalProvider和externalUserId查找您的用戶,或者配置新用戶
清理和登陸:
//爲用戶發佈身份驗證Cookie await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, props, additionalClaims.ToArray()); // 刪除外部認證期間使用的臨時cookie await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); // 驗證返回URL並重定向回受權端點或本地頁面 if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } return Redirect("~/");
狀態,URL長度和ISecureDataFormat
當重定向到外部提供商登陸時,客戶端應用程序中的頻繁狀態必須進行往返。 這意味着狀態在離開客戶端以前被捕獲並保存直到用戶返回到客戶端應用程序。 包括OpenID Connect在內的許多協議都容許將某種狀態做爲參數傳遞,做爲請求的一部分,身份提供者將在響應中返回該狀態。 ASP.NET Core提供的OpenID Connect身份驗證處理程序利用了協議的這一功能,這就是它如何實現上面提到的returnUrl功能。
在請求參數中存儲狀態的問題是請求URL可能會變得太大(超過2000個字符的公共限制)。 OpenID Connect身份驗證處理程序的確提供了一個可擴展點,以便將狀態存儲在服務器中,而不是存儲在請求URL中。 你能夠經過實現ISecureDataFormat <AuthenticationProperties>並在OpenIdConnectOptions上配置它來實現這一點。
幸運的是,IdentityServer爲您提供了一個實現,並由在DI容器中註冊的IDistributedCache實現(例如標準MemoryDistributedCache)支持。 要使用IdentityServer提供的安全數據格式實現,只需在配置DI時在IServiceCollection上調用AddOidcStateDataFormatterCache擴展方法便可。 若是沒有參數傳遞,則全部配置的OpenID Connect處理程序將使用IdentityServer提供的安全數據格式實現:
public void ConfigureServices(IServiceCollection services) { // 配置OpenIdConnect處理程序以將狀態參數保存到服務器端的IDistributedCache中。 services.AddOidcStateDataFormatterCache(); services.AddAuthentication() .AddOpenIdConnect("demoidsrv", "IdentityServer", options => { // ... }) .AddOpenIdConnect("aad", "Azure AD", options => { // ... }) .AddOpenIdConnect("adfs", "ADFS", options => { // ... }); }
若是隻配置特定方案,則將這些方案做爲參數傳遞:
public void ConfigureServices(IServiceCollection services) { //配置OpenIdConnect處理程序以將狀態參數保存到服務器端的IDistributedCache中。 services.AddOidcStateDataFormatterCache("aad", "demoidsrv"); services.AddAuthentication() .AddOpenIdConnect("demoidsrv", "IdentityServer", options => { // ... }) .AddOpenIdConnect("aad", "Azure AD", options => { // ... }) .AddOpenIdConnect("adfs", "ADFS", options => { // ... }); }
在支持的平臺上,您可使用IdentityServer使用Windows身份驗證(例如,針對Active Directory)對用戶進行身份驗證。 當前使用如下命令託管IdentityServer時,Windows身份驗證可用:
在這兩種狀況下,使用方案「Windows」在HttpContext上使用ChallengeAsync API來觸發Windows身份驗證。 咱們的快速入門UI中的賬戶控制器實現了必要的邏輯。
Using Kestrel
使用Kestrel時,必須運行「behind」IIS並使用IIS integration:
var host = new WebHostBuilder() .UseKestrel() .UseUrls("http://localhost:5000") .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .Build();
在使用WebHost.CreateDefaultBuilder方法設置WebHostBuilder時,Kestrel會自動配置。
此外,IIS(或IIS Express)中的虛擬目錄必須啓用Windows和匿名身份驗證。
IIS集成層將Windows身份驗證處理程序配置爲DI,能夠經過身份驗證服務調用。 一般在IdentityServer中,建議禁用此自動行爲。 這在ConfigureServices中完成:
services.Configure<IISOptions>(iis => { iis.AuthenticationDisplayName = "Windows"; iis.AutomaticAuthentication = false; });
註銷IdentityServer就像刪除身份驗證cookie同樣簡單,可是爲了完成聯合註銷,咱們必須考慮將用戶從客戶端應用程序(甚至多是上游身份提供商)中註銷。
刪除身份驗證Cookie
要刪除身份驗證cookie,只需在HttpContext上使用SignOutAsync擴展方法便可。 您將須要傳遞使用的方案(由IdentityServerConstants.DefaultCookieAuthenticationScheme提供,除非您已更改它):
await HttpContext.SignOutAsync(IdentityServerConstants.DefaultCookieAuthenticationScheme);
或者您可使用IdentityServer提供的便捷擴展方法:
await HttpContext.SignOutAsync();
通知客戶該用戶已註銷
做爲註銷過程的一部分,您須要確保向客戶端應用程序通知用戶已註銷。 IdentityServer支持服務器端客戶端(例如MVC)的前端通道規範,服務器端客戶端的後端通道規範(例如MVC)以及基於瀏覽器的JavaScript客戶端的會話管理規範(例如SPA,React,Angular 等)。
前端服務器端客戶端
要經過前端通道規範從服務器端客戶端應用程序註銷用戶,IdentityServer中的「註銷」頁面必須呈現<iframe>以通知客戶端用戶已註銷。 但願被通知的客戶端必須設置FrontChannelLogoutUri配置值。 IdentityServer跟蹤用戶登陸的客戶端,並在IIdentityServerInteractionService(詳細信息)上提供名爲GetLogoutContextAsync的API。 該API返回一個帶有SignOutIFrameUrl屬性的LogoutRequest對象,您註銷的頁面必須呈現爲<iframe>。
後端服務器端客戶端
要經過後端通道規範從服務器端客戶端應用程序註銷用戶,IdentityServer中的SignOutIFrameUrl端點將自動觸發服務器到服務器的調用,將簽名註銷請求傳遞給客戶端。 這意味着即便沒有前端通道客戶端,IdentityServer中的「註銷」頁面也必須如上所述向SignOutIFrameUrl呈現<iframe>。 但願收到通知的客戶端必須設置BackChannelLogoutUri配置值。
基於瀏覽器的JavaScript客戶端
鑑於會話管理規範的設計方式,IdentityServer中沒有什麼特別的,您須要作的是通知這些客戶端用戶已註銷。 可是,客戶端必須對check_session_iframe執行監視,而且這由oidc-client JavaScript庫實現。
由客戶端應用程序發起的註銷
若是註銷是由客戶端應用程序啓動的,則客戶端首先將用戶重定向到最終會話端點。 在會話結束端點進行處理可能須要經過重定向到註銷頁面來維護一些臨時狀態(例如,客戶端的註銷註銷重定向uri)。 該狀態可能對註銷頁面有用,而且狀態的標識符經過logoutId參數傳遞到註銷頁面。
交互服務上的GetLogoutContextAsync API可用於加載狀態。ShowSignoutPrompt指示註銷請求是否已經過身份驗證, 所以不會提示用戶註銷。
默認狀況下,此狀態做爲經過logoutId值傳遞的受保護數據結構進行管理。 若是您但願在終端會話終端和註銷頁面之間使用其餘持久性,則能夠實現IMessageStore <LogoutMessage>並在DI中註冊實現。
當用戶註銷IdentityServer,而且他們使用外部身份提供程序登陸時,可能會將其重定向到也註銷外部提供程序。 並不是全部外部提供商都支持註銷,由於它取決於它們支持的協議和功能。
要檢測用戶必須重定向到外部身份提供者進行註銷,一般經過在IdentityServer中使用發佈到cookie中的idp聲明來完成。 此聲明中設置的值是相應身份驗證中間件的AuthenticationScheme。 在註銷時,諮詢此聲明以瞭解是否須要外部註銷。
歸因於正常註銷工做流程已經須要的清理和狀態管理,將用戶重定向至外部身份提供者是有問題的。 而後在IdentityServer上完成正常註銷和清除過程的惟一方法是,而後向外部身份提供者請求在註銷後將用戶重定向回IdentityServer。 並不是全部外部提供商都支持註銷後重定向,由於它取決於它們支持的協議和功能。
而後,註銷時的工做流程將撤消IdentityServer的身份驗證cookie,而後重定向到請求退出後重定向的外部提供程序。 註銷後重定向應保持此處所述的必要註銷狀態(即logoutId參數值)。 要在外部提供者註銷後重定向到IdentityServer,在使用ASP.NET Core的SignOutAsync API時,應在AuthenticationProperties上使用RedirectUri,例如:
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Logout(LogoutInputModel model) { // 構建模型,以便註銷頁面知道要顯示的內容 var vm = await _account.BuildLoggedOutViewModelAsync(model.LogoutId); var user = HttpContext.User; if (user?.Identity.IsAuthenticated == true) { // 刪除本地認證cookie await HttpContext.SignOutAsync(); // 喚起註銷事件 await _events.RaiseAsync(new UserLogoutSuccessEvent(user.GetSubjectId(), user.GetName())); } //檢查咱們是否須要在上游身份提供商處觸發註銷 if (vm.TriggerExternalSignout) { // 創建一個返回URL,以便上游提供商將重定向回來 // 在用戶註銷後給咱們。 這可讓咱們接下來 // 完成咱們的單一註銷處理。 string url = Url.Action("Logout", new { logoutId = vm.LogoutId }); // 這會觸發重定向到外部提供程序以進行註銷 return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); } return View("LoggedOut", vm); }
一旦用戶註銷外部提供者而後重定向,IdentityServer的正常註銷處理應該執行,其中涉及處理logoutId並進行全部必要的清理。
聯合註銷是指用戶使用外部身份提供程序登陸身份服務器的狀況,而後,用戶經過身份服務器未知的工做流注銷該外部標識提供者。當用戶註銷時,通知身份服務器將很是有用,這樣它就能夠將用戶從標識服務器和全部使用標識服務器的應用程序中註銷。
並不是全部外部身份提供者都支持聯合註銷,可是那些這樣作的人將提供一種機制來通知客戶端用戶已經註銷了。此通知一般以來自外部身份提供者的「註銷」頁面的<iframe>請求的形式出現。而後,IdentityServer必須通知其全部客戶端(如此處所述),一般還要從外部身份提供者的<iframe>中以<iframe>的形式發送請求。
使聯合註銷成爲特殊狀況(與正常註銷相比)的緣由是聯合註銷請求不是IdentityServer中的正常註銷端點。 事實上,每一個外部IdentityProvider將在您的IdentityServer主機中具備不一樣的端點。 這是由於每一個外部身份提供者可能使用不一樣的協議,而且每一箇中間件都在不一樣的端點上偵聽。
全部這些因素的淨效應是,沒有像咱們在正常註銷工做流程中那樣呈現「註銷」頁面,這意味着咱們錯過了IdentityServer客戶端的註銷通知。 咱們必須爲每一個這些聯合註銷端點添加代碼,以呈現必要的通知以實現聯合註銷。
幸運的是,IdentityServer已經包含此代碼。 當請求進入IdentityServer並調用外部認證提供程序的處理程序時,IdentityServer會檢測這些聯合註銷請求是否爲聯合註銷請求,若是是,它將自動呈現與此處所述的相同的<iframe>用於註銷。 簡而言之,自動支持聯合註銷。
通用架構是所謂的聯合網關。 在這種方法中,IdentityServer充當一個或多個外部身份提供者的網關。
該架構具備如下優勢
您控制網關(而不是某些外部服務提供商) - 這意味着您能夠對其進行任何更改,並保護您的應用程序免受外部提供程序可能對其本身的服務所作的更改。
換句話說 - 擁有您的聯合網關可讓您對您的身份基礎架構進行不少控制。 因爲您的用戶身份是您最重要的資產之一,咱們建議您控制網關。
咱們的快速入門UI使用瞭如下一些功能。 還請查看外部認證快速入門和有關外部提供商的文檔。
在受權請求期間,若是IdentityServer須要用戶贊成,則瀏覽器將被重定向到贊成頁面。
贊成用於容許最終用戶授予客戶端對資源(身份或API)的訪問權限。 這一般只對第三方客戶端是必需的,而且能夠在客戶端設置上對每一個客戶端啓用/禁用。
Consent頁面
爲了讓用戶贊成,託管應用程序必須提供贊成頁面。 快速入門UI具備贊成頁面的基本實現。
贊成頁面一般呈現當前用戶的顯示名稱,請求訪問的客戶端的顯示名稱,客戶端的徽標,有關客戶端的更多信息的連接以及客戶端請求訪問的資源列表。 容許用戶代表他們的贊成應該被「記住」,以便未來不會再爲相同的客戶再次提示他們也是常見的。
一旦用戶提供了贊成,贊成頁面必須通知IdentityServer贊成,而後瀏覽器必須重定向回受權端點。
受權上下文
IdentityServer將傳遞一個returnUrl參數(可在用戶交互選項上配置)到包含受權請求參數的贊成頁面。 這些參數提供了贊成頁面的上下文,而且能夠在交互服務的幫助下閱讀。 GetAuthorizationContextAsync API將返回AuthorizationRequest的一個實例。
可使用IClientStore和IResourceStore接口獲取有關客戶端或資源的其餘詳細信息。
向IdentityServer通知贊成結果
交互服務上的GrantConsentAsync API容許贊成頁面通知IdentityServer贊成的結果(也多是拒絕客戶端訪問)。
IdentityServer將暫時持久贊成的結果。 這個持久性默認使用cookie,由於它只需保存足夠長的時間以將結果傳遞迴受權端點。 此臨時持久性與用於「記住個人贊成」功能的持久性不一樣(而且它是持久存儲用戶的「記住個人贊成」的受權端點)。 若是您但願在許可頁面和受權重定向之間使用其餘持久性,則能夠實現IMessageStore <ConsentResponse>並在DI中註冊實現。
將用戶返回到受權端點
一旦贊成頁面通知了IdentityServer的結果,用戶能夠被重定向回returnUrl。 您的贊成頁面應經過驗證returnUrl是否有效來防止打開重定向。 這能夠經過在交互服務上調用IsValidReturnUrl來完成。 此外,若是GetAuthorizationContextAsync返回非null結果,那麼您還能夠信任returnUrl有效。
IdentityServer默認以JWT(JSON Web令牌)格式發出訪問令牌。
今天的每一個相關平臺都支持驗證JWT令牌,這裏能夠找到一個很好的JWT庫列表:
保護基於ASP.NET Core的API只是在DI中配置JWT承載身份驗證處理程序,並將身份驗證中間件添加到管道:
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // base-address of your identityserver options.Authority = "https://demo.identityserver.io"; // name of the API resource options.Audience = "api1"; }); } public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { app.UseAuthentication(); app.UseMvc(); } }
IdentityServer身份驗證處理程序
咱們的身份驗證處理程序與上述處理程序具備相同的用途(實際上它在內部使用Microsoft JWT庫),但添加了一些附加功能:
對於最簡單的狀況,咱們的處理程序配置與上面的代碼片斷很是類似:
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(options => { // base-address of your identityserver options.Authority = "https://demo.identityserver.io"; // name of the API resource options.ApiName = "api1"; }); } public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) { app.UseAuthentication(); app.UseMvc(); } }
支持參考標記
若是傳入令牌不是JWT,咱們的中間件將調用在發現文檔中找到的自省端點以驗證該令牌。 因爲自檢端點須要身份驗證,所以您須要提供已配置的API secret,例如:
.AddIdentityServerAuthentication(options => { // base-address of your identityserver options.Authority = "https://demo.identityserver.io"; // name of the API resource options.ApiName = "api1"; options.ApiSecret = "secret"; })
一般,您不但願爲每一個傳入請求執行自檢端點的往返。 中間件有一個內置緩存,您能夠像這樣啓用:
.AddIdentityServerAuthentication(options => { // base-address of your identityserver options.Authority = "https://demo.identityserver.io"; // name of the API resource options.ApiName = "api1"; options.ApiSecret = "secret"; options.EnableCaching = true; options.CacheDuration = TimeSpan.FromMinutes(10); // that's the default })
處理程序將使用DI容器中註冊的任何IDistributedCache實現(例如,標準MemoryDistributedCache)。
驗證scopes
ApiName屬性檢查令牌是否具備匹配的受衆(或短審計)聲明。
在IdentityServer中,您還能夠將API細分爲多個範圍。 若是您須要這種粒度,則可使用ASP.NET Core受權策略系統來檢查範圍。
制定全局政策:
services .AddMvcCore(options => { // require scope1 or scope2 var policy = ScopePolicy.Create("scope1", "scope2"); options.Filters.Add(new AuthorizeFilter(policy)); }) .AddJsonFormatters() .AddAuthorization();
撰寫範圍政策:
services.AddAuthorization(options => { options.AddPolicy("myPolicy", builder => { // require scope1 builder.RequireScope("scope1"); // and require scope2 or scope3 builder.RequireScope("scope2", "scope3"); }); });
您的身份服務器只是一個標準的ASP.NET核心應用程序,包括IdentityServer中間件。 首先閱讀有關發佈和部署的官方Microsoft文檔。
您的典型架構
一般,您將設計IdentityServer部署以實現高可用性:
IdentityServer自己是無狀態的,不須要服務器關聯 - 可是有些數據須要在實例之間共享。
配置數據
這一般包括:
存儲數據的方式取決於您的環境。 在配置數據不多更改的狀況下,咱們建議使用內存存儲和代碼或配置文件。
在高度動態的環境(例如Saas)中,咱們建議使用數據庫或配置服務動態加載配置。
IdentityServer支持代碼配置和配置文件(請參閱此處)。 對於數據庫,咱們爲基於Entity Framework Core的數據庫提供支持。
您還能夠經過實現IResourceStore和IClientStore來構建本身的配置存儲。
Key material
參考:https://identityserver4.readthedocs.io/en/release/topics/crypto.html#refcrypto
運營數據
對於某些操做,IdentityServer須要持久性存儲來保持狀態,這包括:
您可使用傳統數據庫來存儲運營數據,也可使用具備持久性功能(如Redis)的緩存。 上面提到的EF核心實施也支持運營數據。
您也能夠經過實施IPersistedGrantStore來實現對自定義存儲機制的支持 - 默認狀況下IdentityServer會注入內存中的版本。
ASP.NET core數據保護
ASP.NET Core自己須要共享key material來保護cookie,狀態字符串等敏感數據。請參閱此處的官方文檔。
您能夠重複使用上述持久性存儲之一,也可使用像共享文件這樣的簡單文件。
IdentityServer使用ASP.NET Core提供的標準日誌記錄工具。 Microsoft文檔有一個很好的介紹和內置日誌記錄提供程序的說明。
咱們大體遵循Microsoft使用日誌級別的準則:
Trace
僅供開發人員解決問題的信息。 這些消息可能包含敏感的應用程序數據(如令牌),不該在生產環境中啓用。Debug
遵循內部流程並理解爲何作出某些決定。 在開發和調試過程當中有短時間的用處。Information
用於跟蹤應用程序的通常流程。 這些日誌一般具備一些長期價值。Warning針對應用程序流程中的異常或意外事件。 這些可能包括錯誤或其餘不會致使應用程序中止的狀況,但可能須要進行調查。
Error
對於沒法處理的錯誤和異常。 示例:協議請求的驗證失敗。Critical對於須要當即關注的故障。 示例:缺乏商店實施,無效的key material......
Serilog的設置
ASP.NET Core 2.0+
對於如下配置,您須要Serilog.AspNetCore和Serilog.Sinks.Console包:
public class Program { public static void Main(string[] args) { Console.Title = "IdentityServer4"; Log.Logger = new LoggerConfiguration() .MinimumLevel.Debug() .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) .MinimumLevel.Override("System", LogEventLevel.Warning) .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) .Enrich.FromLogContext() .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Literate) .CreateLogger(); BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) { return WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .UseSerilog() .Build(); } }
日誌記錄是更低級別的「printf」樣式 - 事件表明有關IdentityServer中某些操做的更高級別信息。 事件是結構化數據,包括事件ID,成功/失敗信息,類別和詳細信息。 這使查詢和分析它們變得很容易,並提取可用於進一步處理的有用信息。
Events work great with event stores like ELK, Seq or Splunk.
發出事件
默認狀況下不會啓用事件 - 但能夠在ConfigureServices方法中全局配置,例如:
services.AddIdentityServer(options => { options.Events.RaiseSuccessEvents = true; options.Events.RaiseFailureEvents = true; options.Events.RaiseErrorEvents = true; });
要發出一個事件,請使用DI容器中的IEventService並調用RaiseAsync方法,例如:
public async Task<IActionResult> Login(LoginInputModel model) { if (_users.ValidateCredentials(model.Username, model.Password)) { // issue authentication cookie with subject ID and username var user = _users.FindByUsername(model.Username); await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username)); } else { await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); } }
自定義sinks
咱們的默認事件接收器將簡單地將事件類序列化爲JSON並將其轉發給ASP.NET Core日誌記錄系統。 若是要鏈接到自定義事件存儲,請實現IEventSink接口並將其註冊到DI。
如下示例使用Seq發出事件:
public class SeqEventSink : IEventSink { private readonly Logger _log; public SeqEventSink() { _log = new LoggerConfiguration() .WriteTo.Seq("http://localhost:5341") .CreateLogger(); } public Task PersistAsync(Event evt) { if (evt.EventType == EventTypes.Success || evt.EventType == EventTypes.Information) { _log.Information("{Name} ({Id}), Details: {@details}", evt.Name, evt.Id, evt); } else { _log.Error("{Name} ({Id}), Details: {@details}", evt.Name, evt.Id, evt); } return Task.CompletedTask; } }
將Serilog.Sinks.Seq包添加到主機以使上述代碼生效。
內置事件
IdentityServer中定義瞭如下事件:
ApiAuthenticationFailureEvent
& ApiAuthenticationSuccessEvent
獲取用於在自檢端點進行成功/失敗的API身份驗證。
ClientAuthenticationSuccessEvent
& ClientAuthenticationFailureEvent
獲取令牌端點上的成功/失敗客戶端身份驗證。
TokenIssuedSuccessEvent
& TokenIssuedFailureEvent
獲取用於請求標識符、訪問標記、刷新標記和受權代碼的成功/失敗嘗試。
TokenIntrospectionSuccessEvent
& TokenIntrospectionFailureEvent
獲取成功的令牌內省請求。
TokenRevokedSuccessEvent
獲取成功的令牌撤銷請求。
UserLoginSuccessEvent
& UserLoginFailureEvent
由quickstart UI引起,用於成功/失敗的用戶登陸。
UserLogoutSuccessEvent
獲取成功的註銷請求。
ConsentGrantedEvent
& ConsentDeniedEvent
在贊成UI中引起。
UnhandledExceptionEvent
獲取未處理的異常。
十6、Cryptography, Keys and HTTPS
IdentityServer依賴於幾個加密機制來完成其工做。
令牌簽名和驗證
IdentityServer須要非對稱密鑰對來簽署和驗證JWT。 該密鑰對能夠是證書/私鑰組合或原始RSA密鑰。 不管如何,它必須支持帶有SHA256的RSA。
加載簽名密鑰和相應的驗證部分由ISigningCredentialStore和IValidationKeysStore的實現完成。 若是您想自定義加載密鑰,您能夠實現這些接口並使用DI註冊它們。
DI構建器擴展有幾種方便的方法來設置簽名和驗證密鑰 - 請參閱此處。
Signing key rollover
雖然一次只能使用一個簽名鍵,可是能夠爲發現文檔發佈多個驗證鍵。這對於鍵翻轉頗有用。
rollover一般以下所示:
這要求客戶端和API使用發現文檔,而且還具備按期刷新其配置的功能。
數據保護
ASP.NET Core中的Cookie身份驗證(或MVC中的防僞)使用ASP.NET Core數據保護功能。 根據您的部署方案,這可能須要額外的配置。 有關更多信息,請參閱Microsoft文檔。
HTTPS
咱們不強制使用HTTPS,但對於生產來講,它與IdentityServer的每次交互都是強制性的。
授予類型是一種指定客戶端如何與IdentityServer交互的方式。 OpenID Connect和OAuth 2規範定義瞭如下受權類型:
您能夠經過客戶端配置上的AllowedGrantTypes屬性指定客戶端可使用的受權類型。
客戶端能夠配置爲使用多種受權類型(例如,用於以用戶爲中心的操做的混合以及用於服務器到服務器通訊的客戶端憑證)。 GrantTypes類可用於從典型的授予類型組合中進行選擇:
Client.AllowedGrantTypes = GrantTypes.HybridAndClientCredentials;
您也能夠手動指定受權類型列表:
Client.AllowedGrantTypes = { GrantType.Hybrid, GrantType.ClientCredentials, "my_custom_grant_type" };
若是您想經過瀏覽器通道傳輸訪問令牌,則還須要明確地在客戶端配置上容許使用該令牌:
Client.AllowAccessTokensViaBrowser = true;
其他部分,簡要介紹受權類型,以及什麼時候使用它們。 還建議您另外閱讀相應的規格以更好地理解差別。
Client credentials
這是最簡單的受權類型,用於服務器到服務器通訊 - 始終表明客戶端而不是用戶請求令牌。
使用此受權類型,您能夠向令牌端點發送令牌請求,並獲取表明客戶端的訪問令牌。 客戶端一般必須使用其客戶端ID和密鑰對令牌端點進行身份驗證。
Resource owner password
資源全部者密碼授予類型容許經過將用戶名和密碼發送給令牌端點來表明用戶請求令牌。 這就是所謂的「非交互式」認證,一般不推薦。
一般對於信任應用使用此受權模式,但通常建議是使用implicit或hybrid 流程來替代用戶身份驗證。
查看資源全部者密碼快速入門以獲取如何使用它的示例。 您還須要提供用於實現IResourceOwnerPasswordValidator接口的用戶名/密碼驗證代碼。 你能夠在這裏找到更多關於這個接口的信息。
Implicit
隱式受權類型針對基於瀏覽器的應用程序進行了優化。 用於僅用戶身份驗證(服務器端和JavaScript應用程序)或身份驗證和訪問令牌請求(JavaScript應用程序)。
在隱式流程中,全部令牌都經過瀏覽器傳輸,所以不容許刷新令牌等高級功能。
本快速入門顯示了服務端Web應用程序的身份驗證,並顯示了JavaScript。
Authorization code
受權代碼流最初由OAuth 2指定,並提供了一種在反向通道上檢索令牌而不是瀏覽器前端通道的方法。 它也支持客戶端認證。
雖然這種受權類型自己是受支持的,但一般建議您將其與身份令牌結合使用,將其轉換爲所謂的混合流。 混合流程爲您提供重要的額外功能,如簽名的協議響應
Hybrid
混合流是隱式和受權代碼流的組合 - 它使用多個授予類型的組合,最典型的是code id_token。
在混合流中,身份令牌經過瀏覽器通道傳輸,幷包含簽名的協議響應以及受權代碼等其餘部件的簽名。這能夠緩解適用於瀏覽器端的大量攻擊。成功驗證響應後,使用back-channel來檢索訪問和刷新令牌。
對於想要檢索訪問令牌(也多是刷新令牌)的本地應用程序,這是推薦的流程,用於服務器端Web應用程序和本地桌面/移動應用程序。
Refresh tokens
刷新令牌容許得到API的長期訪問權限。
刷新令牌容許請求新的訪問令牌,而無需用戶交互。 每次客戶端刷新令牌時,都須要對IdentityServer進行(認證)後向通道調用。 這容許檢查刷新標記是否仍然有效,或者在此期間已被撤銷。
在混合、受權代碼和資源全部者密碼流中支持刷新令牌。要請求刷新令牌,客戶端須要在令牌請求中包含offline_access範圍(而且必須被受權請求該範圍)。
Extension grants
擴展授予容許使用新的授予類型擴展令牌端點。有關更多細節,請參見本文。
Incompatible grant types
禁止一些受權類型組合:
secret解析和驗證是身份服務器中的可擴展點,開箱即用它支持共享secret以及經過基自己份驗證頭或POST主體傳輸共享secret。
建立一個共享的secret
如下代碼設置了散列共享密鑰:
var secret = new Secret("secret".Sha256());
這個密鑰如今能夠分配給客戶端或ApiResource。 請注意,二者不只支持單個祕密,並且還支持多個密鑰。 這對密鑰翻轉和旋轉頗有用:
var client = new Client { ClientId = "client", ClientSecrets = new List<Secret> { secret }, AllowedGrantTypes = GrantTypes.ClientCredentials, AllowedScopes = new List<string> { "api1", "api2" } };
實際上,您也能夠爲密鑰分配說明和到期日期。 描述將用於日誌記錄,以及執行密鑰生命週期的截止日期:
var secret = new Secret( "secret".Sha256(), "2016 secret", new DateTime(2016, 12, 31));
使用共享密鑰進行身份驗證
您能夠將客戶端ID /secret組合做爲POST正文的一部分發送:
POST /connect/token client_id=client1& client_secret=secret& ...
..或做爲basic身份驗證頭:
POST /connect/token
Authorization: Basic xxxxx
...
您可使用如下C#代碼手動建立basic身份驗證頭:
var credentials = string.Format("{0}:{1}", clientId, clientSecret); var headerValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(credentials)); var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", headerValue);
IdentityModel庫具備名爲TokenClient和IntrospectionClient的輔助類,它們封裝了身份驗證和協議消息。
除了共享secret
還有其餘技術來驗證客戶端,例如 基於公鑰/私鑰加密。 IdentityServer包括對私鑰JWT客戶機密鑰的支持(請參閱RFC 7523)。
secret可擴展性一般包含三件事:
secret解析器和驗證器是ISecretParser和ISecretValidator接口的實現。 要使它們可用於IdentityServer,您須要將它們註冊到DI容器,例如:
builder.AddSecretParser<ClientAssertionSecretParser>()
builder.AddSecretValidator<PrivateKeyJwtSecretValidator>()
咱們的默認私鑰JWTsecret驗證器指望完整的(leaf)證書做爲secret定義上的base64。而後,該證書將用於驗證自簽名JWT上的簽名,例如:
var client = new Client { ClientId = "client.jwt", ClientSecrets = { new Secret { Type = IdentityServerConstants.SecretTypes.X509CertificateBase64, Value = "MIIDATCCAe2gAwIBAgIQoHUYAquk9rBJcq8W+F0FAzAJBgUrDgMCHQUAMBIxEDAOBgNVBAMTB0RldlJvb3QwHhcNMTAwMTIwMjMwMDAwWhcNMjAwMTIwMjMwMDAwWjARMQ8wDQYDVQQDEwZDbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDSaY4x1eXqjHF1iXQcF3pbFrIbmNw19w/IdOQxbavmuPbhY7jX0IORu/GQiHjmhqWt8F4G7KGLhXLC1j7rXdDmxXRyVJBZBTEaSYukuX7zGeUXscdpgODLQVay/0hUGz54aDZPAhtBHaYbog+yH10sCXgV1Mxtzx3dGelA6pPwiAmXwFxjJ1HGsS/hdbt+vgXhdlzud3ZSfyI/TJAnFeKxsmbJUyqMfoBl1zFKG4MOvgHhBjekp+r8gYNGknMYu9JDFr1ue0wylaw9UwG8ZXAkYmYbn2wN/CpJl3gJgX42/9g87uLvtVAmz5L+rZQTlS1ibv54ScR2lcRpGQiQav/LAgMBAAGjXDBaMBMGA1UdJQQMMAoGCCsGAQUFBwMCMEMGA1UdAQQ8MDqAENIWANpX5DZ3bX3WvoDfy0GhFDASMRAwDgYDVQQDEwdEZXZSb290ghAsWTt7E82DjU1E1p427Qj2MAkGBSsOAwIdBQADggEBADLje0qbqGVPaZHINLn+WSM2czZk0b5NG80btp7arjgDYoWBIe2TSOkkApTRhLPfmZTsaiI3Ro/64q+Dk3z3Kt7w+grHqu5nYhsn7xQFAQUf3y2KcJnRdIEk0jrLM4vgIzYdXsoC6YO+9QnlkNqcN36Y8IpSVSTda6gRKvGXiAhu42e2Qey/WNMFOL+YzMXGt/nDHL/qRKsuXBOarIb++43DV3YnxGTx22llhOnPpuZ9/gnNY7KLjODaiEciKhaKqt/b57mTEz4jTF4kIg6BP03MUfDXeVlM1Qf1jB43G2QQ19n5lUiqTpmQkcfLfyci2uBZ8BkOhXr3Vk9HIk/xBXQ=" } }, AllowedGrantTypes = GrantTypes.ClientCredentials, AllowedScopes = { "api1", "api2" } };
您能夠實現本身的secret驗證器(或擴展咱們的secret驗證器)來實現例如 相反,鏈信任驗證。
OAuth 2.0定義了令牌端點的標準受權類型,例如password,authorization_code和refresh_token。 擴展受權是一種添加對非標準令牌頒發方案(如令牌轉換,委派或自定義憑據)的支持的方法。
您能夠經過實現IExtensionGrantValidator接口來添加對其餘受權類型的支持:
public interface IExtensionGrantValidator { /// <summary> /// Handles the custom grant request. /// </summary> /// <param name="request">The validation context.</param> Task ValidateAsync(ExtensionGrantValidationContext context); /// <summary> /// Returns the grant type this validator can deal with /// </summary> /// <value> /// The type of the grant. /// </value> string GrantType { get; } }
他的ExtensionGrantValidationContext對象使您能夠訪問:
要註冊擴展受權,請將其添加到DI:
builder.AddExtensionGrantValidator<MyExtensionsGrantValidator>();
示例:使用擴展受權進行簡單委派
想象一下如下場景 - 前端客戶端使用經過交互流(例如混合流)獲取的令牌來調用中間層API。 此中間層API(API 1)如今但願表明交互式用戶調用後端API(API 2):
換句話說,中間層API(API 1)須要包含用戶身份的訪問令牌,但須要具備後端API(API 2)的scope。
實施擴展受權
前端會將令牌發送到API 1,如今須要在IdentityServer上使用API 2的新令牌交換此令牌。
在線上,對交換的令牌服務的調用可能以下所示:
POST /connect/token grant_type=delegation& scope=api2& token=...& client_id=api1.client client_secret=secret
擴展受權驗證程序的工做是經過驗證傳入令牌並返回表示新令牌的結果來處理該請求:
public class DelegationGrantValidator : IExtensionGrantValidator { private readonly ITokenValidator _validator; public DelegationGrantValidator(ITokenValidator validator) { _validator = validator; } public string GrantType => "delegation"; public async Task ValidateAsync(ExtensionGrantValidationContext context) { var userToken = context.Request.Raw.Get("token"); if (string.IsNullOrEmpty(userToken)) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant); return; } var result = await _validator.ValidateAccessTokenAsync(userToken); if (result.IsError) { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant); return; } // get user's identity var sub = result.Claims.FirstOrDefault(c => c.Type == "sub").Value; context.Result = new GrantValidationResult(sub, GrantType); return; } }
不要忘記在DI上註冊驗證器。
註冊委託客戶端
您須要在IdentityServer中進行客戶端註冊,以容許客戶端使用此新的擴展受權,例如:
var client = new client { ClientId = "api1.client", ClientSecrets = new List<Secret> { new Secret("secret".Sha256()) }, AllowedGrantTypes = { "delegation" }, AllowedScopes = new List<string> { "api2" } }
調用令牌端點
在API 1中,您如今能夠本身構建HTTP有效內容,或使用IdentityModel幫助程序庫:
public async Task<TokenResponse> DelegateAsync(string userToken) { var payload = new { token = userToken }; // create token client var client = new TokenClient(disco.TokenEndpoint, "api1.client", "secret"); // send custom grant to token endpoint, return response return await client.RequestCustomGrantAsync("delegation", "api2", payload); }
TokenResponse.AccessToken如今將包含委託訪問令牌。
二10、Resource Owner Password Validation
若是要使用OAuth 2.0資源全部者密碼憑據受權(也稱爲密碼),則須要實現並註冊IResourceOwnerPasswordValidator接口:
public interface IResourceOwnerPasswordValidator { /// <summary> /// Validates the resource owner password credential /// </summary> /// <param name="context">The context.</param> Task ValidateAsync(ResourceOwnerPasswordValidationContext context); }
在上下文中,您將找到已解析的協議參數,如UserName和Password,若是您想查看其餘輸入數據,還能夠找到原始請求。
而後,您的工做是實施密碼驗證並相應地在上下文中設置結果。 請參閱GrantValidationResult文檔。
因爲訪問令牌的生命週期有限,所以刷新令牌容許在沒有用戶交互的狀況下請求新的訪問令牌。
如下流程支持刷新令牌:authorization code, hybrid 和resource owner password憑據流。 須要明確受權客戶端經過將AllowOfflineAccess設置爲true來請求刷新令牌。
其餘客戶端設置
AbsoluteRefreshTokenLifetime
刷新令牌的最長生命週期,以秒爲單位。 默認爲2592000秒/ 30天。 Zero與RefreshTokenExpiration = Sliding一塊兒使用時永不過時的刷新令牌
SlidingRefreshTokenLifetime
刷新令牌的生命週期以秒爲單位。 默認爲1296000秒/ 15天
RefreshTokenUsage
ReUse
刷新令牌時刷新令牌句柄將保持不變
OneTime
刷新令牌時將更新刷新令牌句柄
RefreshTokenExpiration
Absolute刷新令牌將在固定時間點到期(由AbsoluteRefreshTokenLifetime指定)
Sliding
刷新令牌時,將刷新刷新令牌的生命週期(按SlidingRefreshTokenLifetime中指定的數量)。 生命週期不會超過AbsoluteRefreshTokenLifetime。
UpdateAccessTokenClaimsOnRefresh
獲取或設置一個值,該值指示是否應在刷新令牌請求上更新訪問令牌(及其聲明)。
訪問令牌能夠有兩種形式 - 自包含或引用。
JWT令牌將是一個自包含的訪問令牌 - 它是一個帶有聲明和過時的受保護數據結構。 一旦API瞭解了密鑰材料,它就能夠驗證自包含的令牌,而無需與發行者進行通訊。 這使得JWT難以撤銷。 它們將一直有效,直到它們過時。
使用引用令牌時 - IdentityServer會將令牌的內容存儲在數據存儲中,而且只會將此令牌的惟一標識符發回給客戶端。 接收此引用的API必須打開與IdentityServer的反向通道通訊以驗證令牌。
您可使用如下設置切換客戶端的令牌類型:
client.AccessTokenType = AccessTokenType.Reference;
IdentityServer提供了OAuth 2.0 introspection 規範的實現,該規範容許API取消引用令牌。 您可使用咱們的專用introspection 中間件或使用身份服務器身份驗證中間件,它能夠驗證JWT和引用令牌。
introspection 端點須要身份驗證 - 由於introspection 端點的客戶端是API,您能夠在ApiResource上配置密碼:
var api = new ApiResource("api1") { ApiSecrets = { new Secret("secret".Sha256()) } }
IdentityServer中的許多端點將經過基於JavaScript的客戶端的Ajax調用進行訪問。 鑑於IdentityServer最有可能託管在與這些客戶端不一樣的源上,這意味着須要配置跨源資源共享(CORS)。
基於客戶端的CORS配置
配置CORS的一種方法是在客戶端配置上使用AllowedCorsOrigins集合。 只需將客戶端的原點添加到集合中,IdentityServer中的默認配置將查詢這些值以容許來自源的跨源調用。
配置CORS時,請務必使用origin(不是URL)。 例如:https://foo:123/是一個URL,而https://foo:123是一個origin。
若是您使用咱們提供的「in-memory」或基於EF的客戶端配置,則將使用此默認CORS實現。 若是您定義本身的IClientStore,那麼您將須要實現本身的自定義CORS策略服務(參見下文)。
自定義Cors策略服務
IdentityServer容許託管應用程序實現ICorsPolicyService以徹底控制CORS策略。
要實現的惟一方法是:Task<bool> IsOriginAllowedAsync(string origin)。 若是容許origin則返回true,不然返回false。
實現後,只需在DI中註冊實現,而後IdentityServer將使用您的自定義實現。
DefaultCorsPolicyService
若是您只是但願對一組容許的origin進行硬編碼,那麼可使用一個名爲DefaultCorsPolicyService的預構建的ICorsPolicyService實現。 這將在DI中配置爲單例,並使用其AllowedOrigins集合進行硬編碼,或將AllowAll標誌設置爲true以容許全部來源。 例如,在ConfigureServices中:
var cors = new DefaultCorsPolicyService(_loggerFactory.CreateLogger<DefaultCorsPolicyService>()) { AllowedOrigins = { "https://foo", "https://bar" } }; services.AddSingleton<ICorsPolicyService>(cors);
請謹慎使用AllowAll。
將IdentityServer的CORS策略與ASP.NET Core的CORS策略混合使用
dentityServer使用ASP.NET Core的CORS中間件來提供其CORS實現。 託管IdentityServer的應用程序可能還須要CORS用於本身的自定義端點。 一般,二者應該在同一個應用程序中一塊兒工做。
您的代碼應使用ASP.NET Core中記錄的CORS功能,而不考慮IdentityServer。 這意味着您應該定義策略並正常註冊中間件。 若是您的應用程序在ConfigureServices中定義策略,那麼這些策略應該繼續在您使用它們的相同位置(在您配置CORS中間件的位置或在控制器代碼中使用MVC EnableCors屬性的位置)。 相反,若是您使用CORS中間件(經過策略構建器回調)定義內聯策略,那麼它也應該繼續正常工做。
若是您決定建立自定義ICorsPolicyProvider,那麼在您使用ASP.NET Core CORS服務和IdentityServer之間可能存在衝突的一種狀況。 鑑於ASP.NET Core的CORS服務和中間件的設計,IdentityServer實現了本身的自定義ICorsPolicyProvider並將其註冊到DI系統中。 幸運的是,IdentityServer實現旨在使用裝飾器模式來包裝已在DI中註冊的任何現有ICorsPolicyProvider。 這意味着你也能夠實現ICorsPolicyProvider,但它只須要在DI中的IdentityServer以前註冊(例如在ConfigureServices中)。
能夠在https://baseaddress/.well-known/openid-configuration找到發現文檔。 它包含有關IdentityServer的端點,密鑰材料和功能的信息。
默認狀況下,全部信息都包含在發現文檔中,但經過使用配置選項,您能夠隱藏各個部分,例如:
services.AddIdentityServer(options => { options.Discovery.ShowIdentityScopes = false; options.Discovery.ShowApiScopes = false; options.Discovery.ShowClaims = false; options.Discovery.ShowExtensionGrantTypes = false; });
擴展發現
您能夠向發現文檔添加自定義條目,例如:
services.AddIdentityServer(options => { options.Discovery.CustomEntries.Add("my_setting", "foo"); options.Discovery.CustomEntries.Add("my_complex_setting", new { foo = "foo", bar = "bar" }); });
當您添加以〜開頭的自定義值時,它將擴展爲IdentityServer基址如下的絕對路徑,例如:
options.Discovery.CustomEntries.Add("my_custom_endpoint", "~/custom");
若是要徹底控制發現(和jwks)文檔的呈現,能夠實現IDiscoveryResponseGenerator接口(或從咱們的默認實現派生)。
您能夠向託管IdentityServer4的應用程序添加更多API端點。
您一般但願經過它們所託管的IdentityServer實例來保護這些API。這不是問題。 只需將令牌驗證處理程序添加到主機(請參閱此處):
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); // details omitted services.AddIdentityServer(); services.AddAuthentication() .AddIdentityServerAuthentication("token", isAuth => { isAuth.Authority = "base_address_of_identityserver"; isAuth.ApiName = "name_of_api"; }); }
在您的API上,您須要添加[Authorize]屬性並顯式引用您要使用的身份驗證方案(在此示例中這是令牌,但您能夠選擇您喜歡的任何名稱):
public class TestController : ControllerBase { [Route("test")] [Authorize(AuthenticationSchemes = "token")] public IActionResult Get() { var claims = User.Claims.Select(c => new { c.Type, c.Value }).ToArray(); return Ok(new { message = "Hello API", claims }); } }
若是要從瀏覽器調用該API,則還須要配置CORS。
發現
若是須要,您還能夠將端點添加到發現文檔中,例如:
services.AddIdentityServer(options => { options.Discovery.CustomEntries.Add("custom_endpoint", "~/api/custom"); })
除了對OpenID Connect和OAuth 2.0的內置支持以外,IdentityServer4還容許添加對其餘協議的支持。
您能夠將這些附加協議端點添加爲中間件或使用例如 MVC控制器。 在這兩種狀況下,您均可以訪問ASP.NET Core DI系統,該系統容許重用咱們的內部服務,例如訪問客戶端定義或密鑰材料。
典型認證工做流程
身份驗證請求一般以下所示:
Useful IdentityServer服務
要實現上述工做流程,須要與IdentityServer創建一些交互點。
訪問配置並重定向到登陸頁面
您能夠經過將IdentityServerOptions類注入代碼來訪問IdentityServer配置。 這個,例如 具備登陸頁面的已配置路徑:
var returnUrl = Url.Action("Index"); returnUrl = returnUrl.AddQueryString(Request.QueryString.Value); var loginUrl = _options.UserInteraction.LoginUrl; var url = loginUrl.AddQueryString(_options.UserInteraction.LoginReturnUrlParameter, returnUrl); return Redirect(url);
登陸頁面與當前協議請求之間的交互
IIdentityServerInteractionService支持將協議返回URL轉換爲已解析和驗證的上下文對象:
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
默認狀況下,交互服務僅瞭解OpenID Connect協議消息。 要擴展支持,您能夠編寫本身的IReturnUrlParser:
public interface IReturnUrlParser { bool IsValidReturnUrl(string returnUrl); Task<AuthorizationRequest> ParseAsync(string returnUrl); }
..而後在DI中註冊解析器:
builder.Services.AddTransient<IReturnUrlParser, WsFederationReturnUrlParser>();
這容許登陸頁面獲取客戶端配置和其餘協議參數等信息。
訪問用於建立協議響應的配置和密鑰材料
經過將IKeyMaterialService注入代碼,您能夠訪問配置的簽名憑據和驗證密鑰:
var credential = await _keys.GetSigningCredentialsAsync(); var key = credential.Key as Microsoft.IdentityModel.Tokens.X509SecurityKey; var descriptor = new SecurityTokenDescriptor { AppliesToAddress = result.Client.ClientId, Lifetime = new Lifetime(DateTime.UtcNow, DateTime.UtcNow.AddSeconds(result.Client.IdentityTokenLifetime)), ReplyToAddress = result.Client.RedirectUris.First(), SigningCredentials = new X509SigningCredentials(key.Certificate, result.RelyingParty.SignatureAlgorithm, result.RelyingParty.DigestAlgorithm), Subject = outgoingSubject, TokenIssuerName = _contextAccessor.HttpContext.GetIdentityServerIssuerUri(), TokenType = result.RelyingParty.TokenType };
IdentityServerTools類是在爲IdentityServer編寫可擴展性代碼時可能須要的有用內部工具的集合。 要使用它,請將其注入您的代碼,例如 控制器:
public MyController(IdentityServerTools tools) { _tools = tools; }
IssueJwtAsync方法容許使用IdentityServer令牌建立引擎建立JWT令牌。 IssueClientJwtAsync是用於爲服務器到服務器通訊建立令牌的簡單版本(例如,當您必須從代碼中調用受IdentityServer保護的API時):
public async Task<IActionResult> MyAction() { var token = await _tools.IssueClientJwtAsync( clientId: "client_id", lifetime: 3600, audiences: new[] { "backend.api" }); // more code }