IdentityServer4-主题
一、Startup
二、定义Resources
三、定义Clients
四、登录
五、使用外部身份提供商登录
六、Windows身份验证
七、登出
八、注销外部身份提供商
九、联合注销
十、联合网关
十一、Consent
十二、保护APIs
十三、部署
十四、日志
十五、事件
十六、Cryptography, Keys and HTTPS
十七、Grant Types
十八、Secrets
十九、Extension Grants
二十、Resource Owner Password Validation
二十一、Refresh Tokens
二十二、Reference Tokens
二十三、CORS
二十四、Discovery
二十五、Adding more API Endpoints
二十六、Adding new Protocols
二十七、Tools
一、Startupquickstart 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重定向登录页面();
登录上下文
在您的登录页面上,您可能需要有关请求上下文的信息,以便自定义登录体验(如客户端,提示参数,IdP提示或其他内容)。 这可以通过交互服务上的GetAuthorizationContextAsync API获取。
发布cookie和声明
在ASP.NET Core的HttpContext上有与身份验证相关的扩展方法来发布身份验证cookie并签署用户身份。所使用的身份验证方案必须与您正在使用的cookie处理程序匹配(请参阅上文)。
当您在用户中签名时,您必须发出至少一个子claim和一个名称claim。IdentityServer还在HttpContext上提供一些SignInAsync扩展方法,使其更加方便。
您还可以选择发出idp声明(用于身份提供者名称),amr声明(用于所使用的身份验证方法)和/或auth_time声明(用于用户验证的纪元时间)。 如果您不提供这些,IdentityServer将提供默认值。
五、使用外部身份提供商登录可在用户交互选项上配置)到包含授权请求参数的同意页面。 这些参数提供了同意页面的上下文,并且可以在交互服务的帮助下阅读。 GetAuthorizationContextAsync API将返回AuthorizationRequest的一个实例。
可以使用IClientStore和IResourceStore接口获取有关客户端或资源的其他详细信息。
向IdentityServer通知同意结果
交互服务上的GrantConsentAsync API允许同意页面通知IdentityServer同意的结果(也可能是拒绝客户端访问)。
IdentityServer将暂时持久同意的结果。 这个持久性默认使用cookie,因为它只需保存足够长的时间以将结果传递回授权端点。 此临时持久性与用于“记住我的同意”功能的持久性不同(并且它是持久存储用户的“记住我的同意”的授权端点)。 如果您希望在许可页面和授权重定向之间使用其他持久性,则可以实现IMessageStore
将用户返回到授权端点
一旦同意页面通知了IdentityServer的结果,用户可以被重定向回returnUrl。 您的同意页面应通过验证returnUrl是否有效来防止打开重定向。 这可以通过在交互服务上调用IsValidReturnUrl来完成。 此外,如果GetAuthorizationContextAsync返回非null结果,那么您还可以信任returnUrl有效。
十二、保护APIsJWT bearer authentication handler for ASP.NET Core
保护基于ASP.NET Core的API只是在DI中配置JWT承载身份验证处理程序,并将身份验证中间件添加到管道: IdentityServer身份验证处理程序 我们的身份验证处理程序与上述处理程序具有相同的用途(实际上它在内部使用Microsoft JWT库),但添加了一些附加功能: 对于最简单的情况,我们的处理程序配置与上面的代码片段非常相似: 支持参考标记 如果传入令牌不是JWT,我们的中间件将调用在发现文档中找到的自省端点以验证该令牌。 由于自检端点需要身份验证,因此您需要提供已配置的API secret,例如: 通常,您不希望为每个传入请求执行自检端点的往返。 中间件有一个内置缓存,您可以像这样启用: 处理程序将使用DI容器中注册的任何IDistributedCache实现(例如,标准MemoryDistributedCache)。 验证scopes ApiName属性检查令牌是否具有匹配的受众(或短审计)声明。 在IdentityServer中,您还可以将API细分为多个范围。 如果您需要这种粒度,则可以使用ASP.NET Core授权策略系统来检查范围。 制定全局政策: 撰写范围政策: 十三、部署https://identityserver4.readthedocs.io/en/release/topics/crypto.html#refcrypto 运营数据 对于某些操作,IdentityServer需要持久性存储来保持状态,这包括: 您可以使用传统数据库来存储运营数据,也可以使用具有持久性功能(如Redis)的缓存。 上面提到的EF核心实施也支持运营数据。 您也可以通过实施IPersistedGrantStore来实现对自定义存储机制的支持 - 默认情况下IdentityServer会注入内存中的版本。 ASP.NET core数据保护 ASP.NET Core本身需要共享key material来保护cookie,状态字符串等敏感数据。请参阅此处的官方文档。 您可以重复使用上述持久性存储之一,也可以使用像共享文件这样的简单文件。 十四、日志Microsoft文档有一个很好的介绍和内置日志记录提供程序的说明。 我们大致遵循Microsoft使用日志级别的准则: Serilog的设置 https://serilog.net/ ASP.NET Core 2.0+ 对于以下配置,您需要Serilog.AspNetCore和Serilog.Sinks.Console包: 十五、事件ELK, Seq or Splunk. 发出事件 默认情况下不会启用事件 - 但可以在ConfigureServices方法中全局配置,例如: 要发出一个事件,请使用DI容器中的IEventService并调用RaiseAsync方法,例如: 自定义sinks 我们的默认事件接收器将简单地将事件类序列化为JSON并将其转发给ASP.NET Core日志记录系统。 如果要连接到自定义事件存储,请实现IEventSink接口并将其注册到DI。 以下示例使用Seq发出事件: 将Serilog.Sinks.Seq包添加到主机以使上述代码生效。 内置事件 IdentityServer中定义了以下事件: 获取用于在自检端点进行成功/失败的API身份验证。 获取令牌端点上的成功/失败客户端身份验证。 获取用于请求标识符、访问标记、刷新标记和授权代码的成功/失败尝试。 获取成功的令牌内省请求。 获取成功的令牌撤销请求。 由quickstart UI引发,用于成功/失败的用户登录。 获取成功的注销请求。 在同意UI中引发。 获取未处理的异常。 十六、Cryptography, Keys and HTTPS请参阅此处。 Signing key rollover 虽然一次只能使用一个签名键,但是可以为发现文档发布多个验证键。这对于键翻转很有用。 rollover通常如下所示: 这要求客户端和API使用发现文档,并且还具有定期刷新其配置的功能。 数据保护 ASP.NET Core中的Cookie身份验证(或MVC中的防伪)使用ASP.NET Core数据保护功能。 根据您的部署方案,这可能需要额外的配置。 有关更多信息,请参阅Microsoft文档。 HTTPS 我们不强制使用HTTPS,但对于生产来说,它与IdentityServer的每次交互都是强制性的。 十七、Grant Types更多关于这个接口的信息。 隐式授权类型针对基于浏览器的应用程序进行了优化。 用于仅用户身份验证(服务器端和JavaScript应用程序)或身份验证和访问令牌请求(JavaScript应用程序)。 在隐式流程中,所有令牌都通过浏览器传输,因此不允许刷新令牌等高级功能。 本快速入门显示了服务端Web应用程序的身份验证,并显示了JavaScript。 授权代码流最初由OAuth 2指定,并提供了一种在反向通道上检索令牌而不是浏览器前端通道的方法。 它也支持客户端认证。 虽然这种授权类型本身是受支持的,但通常建议您将其与身份令牌结合使用,将其转换为所谓的混合流。 混合流程为您提供重要的额外功能,如签名的协议响应 混合流是隐式和授权代码流的组合 - 它使用多个授予类型的组合,最典型的是code id_token。 在混合流中,身份令牌通过浏览器通道传输,并包含签名的协议响应以及授权代码等其他部件的签名。这可以缓解适用于浏览器端的大量攻击。成功验证响应后,使用back-channel来检索访问和刷新令牌。 对于想要检索访问令牌(也可能是刷新令牌)的本地应用程序,这是推荐的流程,用于服务器端Web应用程序和本地桌面/移动应用程序。 查看此快速入门以获取有关在MVC中使用混合流的更多信息。 刷新令牌允许获得API的长期访问权限。 刷新令牌允许请求新的访问令牌,而无需用户交互。 每次客户端刷新令牌时,都需要对IdentityServer进行(认证)后向通道调用。 这允许检查刷新标记是否仍然有效,或者在此期间已被撤销。 在混合、授权代码和资源所有者密码流中支持刷新令牌。要请求刷新令牌,客户端需要在令牌请求中包含offline_access范围(并且必须被授权请求该范围)。 扩展授予允许使用新的授予类型扩展令牌端点。有关更多细节,请参见本文。 禁止一些授权类型组合: 十八、SecretsGrantValidationResult文档。 二十一、Refresh Tokens请参阅此处): 在您的API上,您需要添加[Authorize]属性并显式引用您要使用的身份验证方案(在此示例中这是令牌,但您可以选择您喜欢的任何名称): 如果要从浏览器调用该API,则还需要配置CORS。 发现 如果需要,您还可以将端点添加到发现文档中,例如: 二十六、Adding new Protocols可以在此处找到添加WS-Federation支持的示例。 典型认证工作流程 身份验证请求通常如下所示: Useful IdentityServer服务 要实现上述工作流程,需要与IdentityServer建立一些交互点。 访问配置并重定向到登录页面 您可以通过将IdentityServerOptions类注入代码来访问IdentityServer配置。 这个,例如 具有登录页面的已配置路径: 登录页面与当前协议请求之间的交互 IIdentityServerInteractionService支持将协议返回URL转换为已解析和验证的上下文对象: 默认情况下,交互服务仅了解OpenID Connect协议消息。 要扩展支持,您可以编写自己的IReturnUrlParser: ..然后在DI中注册解析器: 这允许登录页面获取客户端配置和其他协议参数等信息。 访问用于创建协议响应的配置和密钥材料 通过将IKeyMaterialService注入代码,您可以访问配置的签名凭据和验证密钥: 二十七、Tools IdentityServerTools类是在为IdentityServer编写可扩展性代码时可能需要的有用内部工具的集合。 要使用它,请将其注入您的代码,例如 控制器: IssueJwtAsync方法允许使用IdentityServer令牌创建引擎创建JWT令牌。 IssueClientJwtAsync是用于为服务器到服务器通信创建令牌的简单版本(例如,当您必须从代码中调用受IdentityServer保护的API时):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();
}
}
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();
}
}
.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
})
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");
});
});
Trace
仅供开发人员解决问题的信息。 这些消息可能包含敏感的应用程序数据(如令牌),不应在生产环境中启用。Debug
遵循内部流程并理解为什么做出某些决定。 在开发和调试过程中有短期的用处。Information
用于跟踪应用程序的一般流程。 这些日志通常具有一些长期价值。Warning针对应用程序流程中的异常或意外事件。 这些可能包括错误或其他不会导致应用程序停止的情况,但可能需要进行调查。
Error
对于无法处理的错误和异常。 示例:协议请求的验证失败。Critical对于需要立即关注的故障。 示例:缺少商店实施,无效的key material......
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
services.AddIdentityServer(options =>
{
options.Events.RaiseSuccessEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseErrorEvents = true;
});
public async Task
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;
}
}
ApiAuthenticationFailureEvent
& ApiAuthenticationSuccessEvent
ClientAuthenticationSuccessEvent
& ClientAuthenticationFailureEvent
TokenIssuedSuccessEvent
& TokenIssuedFailureEvent
TokenIntrospectionSuccessEvent
& TokenIntrospectionFailureEvent
TokenRevokedSuccessEvent
UserLoginSuccessEvent
& UserLoginFailureEvent
UserLogoutSuccessEvent
ConsentGrantedEvent
& ConsentDeniedEvent
UnhandledExceptionEvent
Implicit
Authorization code
Hybrid
Refresh tokens
Extension grants
Incompatible grant types
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";
});
}
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 });
}
}
services.AddIdentityServer(options =>
{
options.Discovery.CustomEntries.Add("custom_endpoint", "~/api/custom");
})
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);
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
public interface IReturnUrlParser
{
bool IsValidReturnUrl(string returnUrl);
Task
builder.Services.AddTransient
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
};
public MyController(IdentityServerTools tools)
{
_tools = tools;
}
public async Task