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 }