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 并在DI中注册实现。

将用户返回到授权端点

一旦同意页面通知了IdentityServer的结果,用户可以被重定向回returnUrl。 您的同意页面应通过验证returnUrl是否有效来防止打开重定向。 这可以通过在交互服务上调用IsValidReturnUrl来完成。 此外,如果GetAuthorizationContextAsync返回非null结果,那么您还可以信任returnUrl有效。

十二、保护APIsJWT bearer authentication handler for ASP.NET Core

  • JWT bearer authentication middleware for Katana
  • IdentityServer authentication middleware for Katana
  • jsonwebtoken for nodejs
  • 保护基于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库),但添加了一些附加功能:

    • 支持JWTs和引用令牌
    • 用于引用标记的可扩展缓存
    • 统一配置模型
    • scope验证

    对于最简单的情况,我们的处理程序配置与上面的代码片段非常相似:

    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");
        });
    });

    十三、部署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使用日志级别的准则:

    • Trace 仅供开发人员解决问题的信息。 这些消息可能包含敏感的应用程序数据(如令牌),不应在生产环境中启用。
    • Debug 遵循内部流程并理解为什么做出某些决定。 在开发和调试过程中有短期的用处。
    • Information 用于跟踪应用程序的一般流程。 这些日志通常具有一些长期价值。
    • Warning针对应用程序流程中的异常或意外事件。 这些可能包括错误或其他不会导致应用程序停止的情况,但可能需要进行调查。
    • Error 对于无法处理的错误和异常。 示例:协议请求的验证失败。
    • Critical对于需要立即关注的故障。 示例:缺少商店实施,无效的key material......

    Serilog的设置

    https://serilog.net/

    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()
                    .UseSerilog()
                    .Build();
        }
    }

    十五、事件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 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

    获取未处理的异常。

    十六、Cryptography, Keys and HTTPS请参阅此处。

    Signing key rollover

    虽然一次只能使用一个签名键,但是可以为发现文档发布多个验证键。这对于键翻转很有用。

    rollover通常如下所示:

    1. 您请求/创建新的key material
    2. 除了当前的验证密钥之外,还要发布新的验证密钥。 您可以使用AddValidationKeys构建器扩展方法。
    3. 所有客户机和api现在都有机会在下次更新发现文档的本地副本时了解新的密钥
    4. 在一定时间(例如24小时)之后,所有客户端和API现在应该接受旧密钥材料和新密钥材料
    5. 只要你愿意,就可以保留旧的密钥材料,也许你有需要验证的长寿命令牌
    6. 当旧密钥材料不再使用时,将其退役
    7. 所有客户端和API将在下次更新发现文档的本地副本时“忘记”旧密钥

    这要求客户端和API使用发现文档,并且还具有定期刷新其配置的功能。

    数据保护

    ASP.NET Core中的Cookie身份验证(或MVC中的防伪)使用ASP.NET Core数据保护功能。 根据您的部署方案,这可能需要额外的配置。 有关更多信息,请参阅Microsoft文档。

    HTTPS

    我们不强制使用HTTPS,但对于生产来说,它与IdentityServer的每次交互都是强制性的。

    十七、Grant Types更多关于这个接口的信息。


    Implicit

     隐式授权类型针对基于浏览器的应用程序进行了优化。 用于仅用户身份验证(服务器端和JavaScript应用程序)或身份验证和访问令牌请求(JavaScript应用程序)。

    在隐式流程中,所有令牌都通过浏览器传输,因此不允许刷新令牌等高级功能。

    本快速入门显示了服务端Web应用程序的身份验证,并显示了JavaScript。


    Authorization code

    授权代码流最初由OAuth 2指定,并提供了一种在反向通道上检索令牌而不是浏览器前端通道的方法。 它也支持客户端认证。

    虽然这种授权类型本身是受支持的,但通常建议您将其与身份令牌结合使用,将其转换为所谓的混合流。 混合流程为您提供重要的额外功能,如签名的协议响应


    Hybrid

    混合流是隐式和授权代码流的组合 - 它使用多个授予类型的组合,最典型的是code id_token。

    在混合流中,身份令牌通过浏览器通道传输,并包含签名的协议响应以及授权代码等其他部件的签名。这可以缓解适用于浏览器端的大量攻击。成功验证响应后,使用back-channel来检索访问和刷新令牌。

    对于想要检索访问令牌(也可能是刷新令牌)的本地应用程序,这是推荐的流程,用于服务器端Web应用程序和本地桌面/移动应用程序。

    查看此快速入门以获取有关在MVC中使用混合流的更多信息。


    Refresh tokens

    刷新令牌允许获得API的长期访问权限。

    刷新令牌允许请求新的访问令牌,而无需用户交互。 每次客户端刷新令牌时,都需要对IdentityServer进行(认证)后向通道调用。 这允许检查刷新标记是否仍然有效,或者在此期间已被撤销。

    在混合、授权代码和资源所有者密码流中支持刷新令牌。要请求刷新令牌,客户端需要在令牌请求中包含offline_access范围(并且必须被授权请求该范围)。


    Extension grants

    扩展授予允许使用新的授予类型扩展令牌端点。有关更多细节,请参见本文。


    Incompatible grant types

    禁止一些授权类型组合:

    • 混合隐式和授权代码或混合将允许从更安全的基于代码的流降级攻击到隐式攻击。
    • 允许授权码和混合模式两者同样令人担忧

    十八、SecretsGrantValidationResult文档。

    二十一、Refresh Tokens请参阅此处):

    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");
    })

    二十六、Adding new Protocols可以在此处找到添加WS-Federation支持的示例。

    典型认证工作流程

    身份验证请求通常如下所示:

    • 身份验证请求到达协议端点
    • 协议端点执行输入验证
    • 重定向到登录页面,返回URL设置回协议端点(如果用户是匿名的)
      • 通过IIdentityServerInteractionService访问当前请求详细信息
      • 用户身份验证(本地或通过外部身份验证中间件)
      • 登录用户
      • 重定向回协议端点
    • 创建协议响应(令牌创建和重定向回客户端)

    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 ParseAsync(string returnUrl);
    }

    ..然后在DI中注册解析器:

    builder.Services.AddTransient();

    这允许登录页面获取客户端配置和其他协议参数等信息。

    访问用于创建协议响应的配置和密钥材料

    通过将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
    };

    二十七、Tools

    IdentityServerTools类是在为IdentityServer编写可扩展性代码时可能需要的有用内部工具的集合。 要使用它,请将其注入您的代码,例如 控制器: 

    public MyController(IdentityServerTools tools)
    {
        _tools = tools;
    }

    IssueJwtAsync方法允许使用IdentityServer令牌创建引擎创建JWT令牌。 IssueClientJwtAsync是用于为服务器到服务器通信创建令牌的简单版本(例如,当您必须从代码中调用受IdentityServer保护的API时):

    public async Task MyAction()
    {
        var token = await _tools.IssueClientJwtAsync(
            clientId: "client_id",
            lifetime: 3600,
            audiences: new[] { "backend.api" });
    
        // more code
    }