IdentityServer4 是 ASP.NET Core 的一個包含 OIDC 和 OAuth 2.0 協議的框架。最近的關注點在 ABP 上,默認 ABP 也集成 IdentityServer4,以前也介紹了不少 IdentityServer3 相關的文章(IdentityServer3 已中止維護)。今天簡單記錄一下 IdentityServer4 相關配置。html
IdentityServer實現如下規範: node
OpenID Connectnginx
- OpenID Connect Core 1.0 (spec)
- OpenID Connect Discovery 1.0 (spec)
- OpenID Connect Session Management 1.0 - draft 22 (spec)
- OpenID Connect HTTP-based Logout 1.0 - draft 03 (spec)
OAuth 2.0git
- OAuth 2.0 (RC-6749)
- OAuth 2.0 Bearer Token Usage (RFC 6750)
- OAuth 2.0 Multiple Response Types (spec)
- OAuth 2.0 Form Post Response Mode (spec)
- OAuth 2.0 Token Revocation (RFC 7009)
- OAuth 2.0 Token Introspection (RFC 7662)
- Proof Key for Code Exchange (RFC 7636)
- JSON Web Tokens for Client Authentication (RFC 7523)
OIDC 是 OpenID Connect 的簡稱,OIDC=(Identity, Authentication) + OAuth 2.0,OpenID Connect 在 OAuth2(兼容) 上構建了一個身份層,是一個基於OAuth2協議的身份認證標準協議。咱們都知道OAuth2是一個受權協議,它沒法提供完善的身份認證功能,OpenID Connect 使用 OAuth2 的受權服務器來爲第三方客戶端提供用戶的身份認證,並把對應的身份認證信息傳遞給客戶端,且能夠適用於各類類型的客戶端(好比服務端應用,移動APP,JS應用),且徹底兼容OAuth2,也就是說你搭建了一個OpenID Connect 的服務後,也能夠看成一個OAuth2的服務來用。OAuth2 提供了 Access Token 來解決受權第三方客戶端訪問受保護資源的問題;OIDC 在這個基礎上提供了 ID Token 來解決第三方客戶端標識用戶身份認證的問題。ID Token 使用 JWT(JSON Web Token) 格式來包裝防篡改,使得 ID Token 能夠安全的傳遞給第三方客戶端程序而且容易被驗證。此外還提供了 UserInfo 的接口,以便獲取用戶更完整的信息。github
協議規範web
流程規範redis
+--------+ +--------+ | | | | | |---------(1) AuthN Request-------->| | | | | | | | +--------+ | | | | | | | | | | | End- |<--(2) AuthN & AuthZ-->| | | | | User | | | | RP | | | | OP | | | +--------+ | | | | | | | |<--------(3) AuthN Response--------| | | | | | | |---------(4) UserInfo Request----->| | | | | | | |<--------(5) UserInfo Response-----| | | | | | +--------+ +--------+
GET /authorize? response_type=code%20id_token%20token &client_id=s6BhdRkqt3 &redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb &scope=openid%20profile%20email &nonce=n-0S6_WzA2Mj &state=af0ifjsldkj HTTP/1.1 Host: server.example.com HTTP/1.1 302 Found Location: https://client.example.org/cb# code=Qcb0Orv1zh30vL1MPRsbm-diHiMwcLyZvn1arpZv-Jxf_11jnpEX3Tgfvk &access_token=jHkWEdUXMU1BwAsC4vtUsZwnNvTIxEl0z9K3vx5KF0Y &token_type=Bearer &id_token=eyJraWQiOiIxZTlnZGs3IiwiYWxnIjoiUlMyNTYifQ.ewogIml zcyI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ 4Mjg5NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiA ibi0wUzZfV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDE zMTEyODA5NzAsCiAiY19oYXNoIjogIkxEa3RLZG9RYWszUGswY25YeENsdEE iCn0.XW6uhdrkBgcGx6zVIrCiROpWURs-4goO1sKA4m9jhJIImiGg5muPUcN egx6sSv43c5DSn37sxCRrDZZm4ZPBKKgtYASMcE20SDgvYJdJS0cyuFw7Ijp _7WnIjcrl6B5cmoM6ylCvsLMwkoQAxVublMwH10oAxjzD6NEFsu9nipkszWh sPePf_rM4eMpkmCbTzume-fzZIi5VjdWGGEmzTg32h3jiex-r5WTHbj-u5HL 7u_KP3rmbdYNzlzd1xWRYTUs4E8nOTgzAUwvwXkIQhOh5TPcSMBYy6X3E7-_ gr9Ue6n4ND7hTFhtjYs3cjNKIA08qm5cpVYFMFMG6PkhzLQ
OIDC 的流程由五個步驟構成:sql
OpenID Connect Flowsmongodb
雖然在 OIDC中 是複用 OAuth2 的流程,可是用途是不同的,且 OIDC 的 AuthN 請求中 scope 參數必需要有一個值爲 openid 的參數。另外定義的模式規範也不同,具體以下:json
ID Token
id_token 是一個安全令牌,是一個受權服務器提供的包含用戶信息(由一組Cliams構成以及其餘輔助的Cliams)的 JWT 格式的數據結構。通常實際生產環境可能還須要咱們本身擴展 Claims(默認 OIDC 提供了一組公共的 Cliams),通常使用 JWK 簽名,驗籤須要 RSA public key (IdentityServer 中從 /.well-known/openid-configuration/jwks 得到 )。
對應 IdentityServer 中配置
public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), new IdentityResources.Email(), new IdentityResources.Phone(), new IdentityResources.Address() }; } ... // configure identity server with in-memory stores, keys, clients and scopes services.AddIdentityServer() .AddInMemoryIdentityResources(Config.GetIdentityResources())
Idr4.HostSrv
/// <summary> /// 配置信息 /// </summary> public class InMemoryConfig { // scopes define the resources in your system public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), //new IdentityResources.Email(), //new IdentityResources.Phone(), //new IdentityResources.Address() }; } /// <summary> /// Define which APIs will use this IdentityServer /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApiResources() { return new[] { new ApiResource("api", "api service") { ApiSecrets = { new Secret("api_pwd".Sha256()) } }, new ApiResource("user", "user service"), new ApiResource("order", "order service") }; } /// <summary> /// Define which Apps will use thie IdentityServer /// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { return new[] { // client credentials client new Client { ClientId = "client_credentials_jwt_grant", ClientSecrets = new [] { new Secret("123456".Sha256()) }, AllowedGrantTypes = GrantTypes.ClientCredentials, AccessTokenType=AccessTokenType.Jwt, AllowedScopes = GetApiResources().Select(t=>t.Name).ToArray() }, // client credentials client new Client { ClientId = "client_credentials_reference_grant", ClientSecrets = new [] { new Secret("123456".Sha256()) }, AllowedGrantTypes = GrantTypes.ClientCredentials, AccessTokenType=AccessTokenType.Reference, AllowedScopes = GetApiResources().Select(t=>t.Name).ToArray() }, // resource owner password grant client new Client { ClientId = "client_password_grant", ClientSecrets = new [] { new Secret("123456".Sha256()) }, AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials, AccessTokenType=AccessTokenType.Reference, AllowedScopes = new [] { "user", "order"}, }, // OpenID Connect hybrid flow and client credentials client (MVC) new Client { ClientId = "mvc", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:5002/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api" }, // Gets or sets a value indicating whether [allow offline access scope]. Defaults to false. AllowOfflineAccess = true }, // Implicit grant client new Client { ClientId = "js", ClientName = "JavaScript Client", AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, RedirectUris = { "http://localhost:5003/callback.html" }, PostLogoutRedirectUris = { "http://localhost:5003/index.html" }, AllowedCorsOrigins = { "http://localhost:5003" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api" }, } }; } /// <summary> /// Define which uses will use this IdentityServer /// </summary> /// <returns></returns> public static IEnumerable<TestUser> GetUsers() { return new[] { new TestUser { SubjectId = "1", Username = "irving", Password = "123456", Claims = new List<Claim> { new Claim("name", "irving"), new Claim("website", "https://irving.com") } }, new TestUser { SubjectId = "2", Username = "ytzhou", Password = "123456", Claims = new List<Claim> { new Claim("name", "ytzhou"), new Claim("website", "https://ytzhou.com") } } }; } }
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); // configure identity server with in-memory stores, keys, clients and scopes services.AddIdentityServer(options => { options.Events.RaiseErrorEvents = true; options.Events.RaiseFailureEvents = true; options.Events.RaiseInformationEvents = true; options.Events.RaiseSuccessEvents = true; }) //.AddDeveloperSigningCredential() //.AddDeveloperSigningCredential(persistKey: false) //.AddDeveloperSigningCredential(persistKey: true, filename: "rsakey.rsa") .AddSigningCredential(new X509Certificate2(Path.Combine(AppContext.BaseDirectory, Configuration["Certs:Path"]), Configuration["Certs:Pwd"])) .AddInMemoryApiResources(InMemoryConfig.GetApiResources()) .AddInMemoryIdentityResources(InMemoryConfig.GetIdentityResources()) .AddInMemoryClients(InMemoryConfig.GetClients()) .AddTestUsers(InMemoryConfig.GetUsers().ToList()); // .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>() //.AddProfileService<ProfileService>(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseCookiePolicy(); app.UseIdentityServer(); // pipeline with a default route named 'default' and the following template: '{controller=Home}/{action=Index}/{id?}'. //app.UseMvcWithDefaultRoute(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); } }
Client
static void Main(string[] args) { Task.Run(() => { return Run(); }); Console.ReadLine(); } public static async Task Run() { var disco = await DiscoveryClient.GetAsync("http://localhost:5000"); //jwt var client = new TokenClient(disco.TokenEndpoint, "client_1", "123456"); var response = await client.RequestClientCredentialsAsync("api"); if (response.IsError) { Console.WriteLine(response.Error); return; } Console.WriteLine(response.Json); } }
報文
GET http://localhost:5000/.well-known/openid-configuration HTTP/1.1 Host: localhost:5000 HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Server: Kestrel X-SourceFiles: =?UTF-8?B?RDpcZ2l0aHViXFNlY3VyaW5nRm9yV2ViQVBJXElkU3J2NC5Ib3N0U3J2XC53ZWxsLWtub3duXG9wZW5pZC1jb25maWd1cmF0aW9u?= X-Powered-By: ASP.NET Date: Tue, 24 Jul 2018 12:43:48 GMT Content-Length: 1313 {"issuer":"http://localhost:5000","jwks_uri":"http://localhost:5000/.well-known/openid-configuration/jwks","authorization_endpoint":"http://localhost:5000/connect/authorize","token_endpoint":"http://localhost:5000/connect/token","userinfo_endpoint":"http://localhost:5000/connect/userinfo","end_session_endpoint":"http://localhost:5000/connect/endsession","check_session_iframe":"http://localhost:5000/connect/checksession","revocation_endpoint":"http://localhost:5000/connect/revocation","introspection_endpoint":"http://localhost:5000/connect/introspect","frontchannel_logout_supported":true,"frontchannel_logout_session_supported":true,"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"scopes_supported":["api","user","order","offline_access"],"claims_supported":[],"grant_types_supported":["authorization_code","client_credentials","refresh_token","implicit","password"],"response_types_supported":["code","token","id_token","id_token token","code id_token","code token","code id_token token"],"response_modes_supported":["form_post","query","fragment"],"token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post"],"subject_types_supported":["public"],"id_token_signing_alg_values_supported":["RS256"],"code_challenge_methods_supported":["plain","S256"]} GET http://localhost:5000/.well-known/openid-configuration/jwks HTTP/1.1 Host: localhost:5000 HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Server: Kestrel X-SourceFiles: =?UTF-8?B?RDpcZ2l0aHViXFNlY3VyaW5nRm9yV2ViQVBJXElkU3J2NC5Ib3N0U3J2XC53ZWxsLWtub3duXG9wZW5pZC1jb25maWd1cmF0aW9uXGp3a3M=?= X-Powered-By: ASP.NET Date: Tue, 24 Jul 2018 12:43:48 GMT Content-Length: 451 {"keys":[{"kty":"RSA","use":"sig","kid":"0fdf841efb8c990ea6f2b09318c0cba2","e":"AQAB","n":"zDMobgJ8pjUAH_e8EqtYZE-t14InmDDcpDqdQp9bT0bGiOpvLpgqgsFJulAwKQfhPwwOwUBKq7Lle461Gb1PRug4L1zN3U-WA9cj0LL4dAHqGCXEazl3FTvWGe8FrQQRTgi8q-I2X_Jhxp8BYQkfatFknVUZSDYudxL-fIDJOSVYus-oEfhupQf_b1Le27UvfMuswVsUhKHbL2wSy_ZtdbY1X8pJ5XoLJwL2AO62Ahfb8ptHBI_Nbc285hAuB4WTPVcIdpp99Oodf6wTiflTVWLGqWP3o48VlxNyixUJCWqWI78BTno06U9cISBTAwbXFLADqjJDYz4OZOAn7Np_DQ","alg":"RS256"}]} POST http://localhost:5000/connect/token HTTP/1.1 Authorization: Basic Y2xpZW50XzE6MTIzNDU2 Accept: application/json Content-Type: application/x-www-form-urlencoded Content-Length: 39 Host: localhost:5000 grant_type=client_credentials&scope=api HTTP/1.1 200 OK Cache-Control: no-store, no-cache, max-age=0 Pragma: no-cache Content-Type: application/json; charset=UTF-8 Server: Kestrel X-SourceFiles: =?UTF-8?B?RDpcZ2l0aHViXFNlY3VyaW5nRm9yV2ViQVBJXElkU3J2NC5Ib3N0U3J2XGNvbm5lY3RcdG9rZW4=?= X-Powered-By: ASP.NET Date: Tue, 24 Jul 2018 12:45:22 GMT Content-Length: 697 {"access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjBmZGY4NDFlZmI4Yzk5MGVhNmYyYjA5MzE4YzBjYmEyIiwidHlwIjoiSldUIn0.eyJuYmYiOjE1MzI0MzYzMjIsImV4cCI6MTUzMjQzOTkyMiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NTAwMC9yZXNvdXJjZXMiLCJhcGkiXSwiY2xpZW50X2lkIjoiY2xpZW50XzEiLCJzY29wZSI6WyJhcGkiXX0.xEucJ6N8HQENfB17dFJlWsGzwI8jdeazLlYdUHcd4OW1BxMjT3CRA0HAkRQl4RHz3GGN9cDjZrg-_mx80Vtk_25sd8eopkuvQAU8OgoHs7c9br0yfnF0SgYglKg-ua5cEPX14EylSDydWltSSERvsOB-4jQBE1N9ZN_ukKzwgKwGtE_k5lHShlhr_00HW3bT-u25SJtl5rAkpoQxPFAo6IVRwm8Wu-pQtLb4tNRn9iavOEehDFT-PlU6ZBlzxAnznN4ul5MLYyRmK5V8tg07ZPyKYPOPk9bFEOhBQtXTyV0KdMtNYpr4KXFgxoMPpSaFZYwQPbCJJm4YmKhCgAdAJg","expires_in":3600,"token_type":"Bearer"} POST http://localhost:5000/connect/token HTTP/1.1 Authorization: Basic Y2xpZW50XzI6MTIzNDU2 Accept: application/json Content-Type: application/x-www-form-urlencoded Content-Length: 41 Host: localhost:5000 grant_type=client_credentials&scope=order HTTP/1.1 200 OK Cache-Control: no-store, no-cache, max-age=0 Pragma: no-cache Content-Type: application/json; charset=UTF-8 Server: Kestrel X-SourceFiles: =?UTF-8?B?RDpcZ2l0aHViXFNlY3VyaW5nRm9yV2ViQVBJXElkU3J2NC5Ib3N0U3J2XGNvbm5lY3RcdG9rZW4=?= X-Powered-By: ASP.NET Date: Tue, 24 Jul 2018 12:57:59 GMT Content-Length: 123 {"access_token":"10f821709ed30dde85e3c1de73a5cb941bf85f77703e544f8cd779d5751ba7c5","expires_in":3600,"token_type":"Bearer"}
上面咱們定義了 ApiResource 配置
/// <summary> /// Define which APIs will use this IdentityServer /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApiResources() { return new[] { new ApiResource("api", "api service") { ApiSecrets = { new Secret("api_pwd".Sha256()) } }, new ApiResource("user", "user service"), new ApiResource("order", "order service") }; }
能夠訪問 /.well-known/openid-configuration 查看到有哪些 scopes 配置。
[ "openid", "profile", "api", "user", "order", "offline_access" ]
其中關於 ApiSecrets 的配置只配置 api 的 scope ,這個配置只適用於後續資源端去驗證 token 是否合法性用到 (調用 introspection endpoint ),只適用於Token 是 Reference token 的方式, Self-contained Json Web Token 採用公鑰的方式驗籤,也就是沒有定義 ApiSecrets 的 scope (如 user,order ) 是不能使用 Reference token 的 Token 的。
資源 API 端
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { //jwt //services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) // .AddIdentityServerAuthentication(options => // { // options.Authority = "http://localhost:5000"; // options.RequireHttpsMetadata = false; // options.ApiName = "api"; // }); //Enable reference tokens services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.ApiName = "api"; options.ApiSecret = "api_pwd"; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseAuthentication(); app.UseMvc(); } }
[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { // GET api/values [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { "value1", "value2" }; } // GET api/values/1 [HttpGet("{id}")] [Authorize] public ActionResult<IEnumerable<string>> Get(int id) { try { return new JsonResult(from c in HttpContext.User.Claims select new { c.Type, c.Value }); } catch (Exception ex) { return new string[] { ex.Message }; } } }
客戶端(僅測試 client credentials grant)
client_credentials_jwt_grant
public static async Task Run() { var disco = await DiscoveryClient.GetAsync("http://localhost:5000"); var client = new TokenClient(disco.TokenEndpoint, "client_credentials_jwt_grant", "123456"); var response = await client.RequestClientCredentialsAsync("api"); if (response.IsError) { Console.WriteLine(response.Error); Console.Read(); } Console.WriteLine(response.Json); //call api var http = new HttpClient(); http.SetBearerToken(response.AccessToken); var message = await http.GetAsync("http://localhost:17181/api/values/1"); if (!message.IsSuccessStatusCode) { Console.WriteLine(message.ReasonPhrase); Console.Read(); } Console.WriteLine(message.Content.ReadAsStringAsync().Result); }
上述 AccessTokenType 定義爲 Jwt,調用 scope 爲 api 接口產生的報文以下
POST http://localhost:5000/connect/token HTTP/1.1 Authorization: Basic Y2xpZW50X2NyZWRlbnRpYWxzX2p3dF9ncmFudDoxMjM0NTY= Accept: application/json Content-Type: application/x-www-form-urlencoded Content-Length: 39 Host: localhost:5000 grant_type=client_credentials&scope=api HTTP/1.1 200 OK Cache-Control: no-store, no-cache, max-age=0 Pragma: no-cache Content-Type: application/json; charset=UTF-8 Server: Kestrel X-SourceFiles: =?UTF-8?B?RDpcZ2l0aHViXFNlY3VyaW5nRm9yV2ViQVBJXElkU3J2NC5Ib3N0U3J2XGNvbm5lY3RcdG9rZW4=?= X-Powered-By: ASP.NET Date: Wed, 25 Jul 2018 11:35:52 GMT Content-Length: 783 {"access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6IkI0RjdDNTUzM0EwNkIyMkU2RDM0OUJFRkQ4NEI3NkU3MzAxNjFCNTUiLCJ0eXAiOiJKV1QiLCJ4NXQiOiJ0UGZGVXpvR3NpNXROSnZ2MkV0MjV6QVdHMVUifQ.eyJuYmYiOjE1MzI1MTg1NTMsImV4cCI6MTUzMjUyMjE1MywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NTAwMC9yZXNvdXJjZXMiLCJhcGkiXSwiY2xpZW50X2lkIjoiY2xpZW50X2NyZWRlbnRpYWxzX2p3dF9ncmFudCIsInNjb3BlIjpbImFwaSJdfQ.rI6oO_L9__lhO6twm8IFfnTDFLHk0zg7rcyue1IC9i85rkgVDeN4Jda4AVNIh4mnY4v7mQT4qizsvDxD7UbSyBkfr9Sq3qjeIHFUsmBzihcSKI2yzOerNCoCm9mdTG7EYgYFWDN_XvD92PTcYh8LGM4rGnCWlozlNU2u2sDHQ55yWvSwAzoGU2xk7vkYhlc1cqYl8gzHfX3lX4iCLPb7uFHOwYX3-ymsBHhVm4rYas9Xf9sefJaJj0Z3HVgp9dhkDxCtU-k5k519xB7MR9HozJSlM74j2z7kAdR8apX2yIeNa-YTzjsqHcoCi1dblc9jQZE05fmFtAdmbf637CrpFw","expires_in":3600,"token_type":"Bearer"} GET http://localhost:17181/api/values/1 HTTP/1.1 Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkI0RjdDNTUzM0EwNkIyMkU2RDM0OUJFRkQ4NEI3NkU3MzAxNjFCNTUiLCJ0eXAiOiJKV1QiLCJ4NXQiOiJ0UGZGVXpvR3NpNXROSnZ2MkV0MjV6QVdHMVUifQ.eyJuYmYiOjE1MzI1MTg1NTMsImV4cCI6MTUzMjUyMjE1MywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NTAwMC9yZXNvdXJjZXMiLCJhcGkiXSwiY2xpZW50X2lkIjoiY2xpZW50X2NyZWRlbnRpYWxzX2p3dF9ncmFudCIsInNjb3BlIjpbImFwaSJdfQ.rI6oO_L9__lhO6twm8IFfnTDFLHk0zg7rcyue1IC9i85rkgVDeN4Jda4AVNIh4mnY4v7mQT4qizsvDxD7UbSyBkfr9Sq3qjeIHFUsmBzihcSKI2yzOerNCoCm9mdTG7EYgYFWDN_XvD92PTcYh8LGM4rGnCWlozlNU2u2sDHQ55yWvSwAzoGU2xk7vkYhlc1cqYl8gzHfX3lX4iCLPb7uFHOwYX3-ymsBHhVm4rYas9Xf9sefJaJj0Z3HVgp9dhkDxCtU-k5k519xB7MR9HozJSlM74j2z7kAdR8apX2yIeNa-YTzjsqHcoCi1dblc9jQZE05fmFtAdmbf637CrpFw Host: localhost:17181 HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Server: Kestrel X-SourceFiles: =?UTF-8?B?RDpcZ2l0aHViXFNlY3VyaW5nRm9yV2ViQVBJXElkU3J2NC5XZWJBcGlcYXBpXHZhbHVlc1wx?= X-Powered-By: ASP.NET Date: Wed, 25 Jul 2018 11:35:55 GMT Content-Length: 297 [{"type":"nbf","value":"1532518553"},{"type":"exp","value":"1532522153"},{"type":"iss","value":"http://localhost:5000"},{"type":"aud","value":"http://localhost:5000/resources"},{"type":"aud","value":"api"},{"type":"client_id","value":"client_credentials_jwt_grant"},{"type":"scope","value":"api"}] GET http://localhost:5000/.well-known/openid-configuration/jwks HTTP/1.1 Host: localhost:5000 HTTP/1.1 200 OK Content-Type: application/json; charset=UTF-8 Server: Kestrel X-SourceFiles: =?UTF-8?B?RDpcZ2l0aHViXFNlY3VyaW5nRm9yV2ViQVBJXElkU3J2NC5Ib3N0U3J2XC53ZWxsLWtub3duXG9wZW5pZC1jb25maWd1cmF0aW9uXGp3a3M=?= X-Powered-By: ASP.NET Date: Wed, 25 Jul 2018 11:35:55 GMT Content-Length: 1858 {"keys":[{"kty":"RSA","use":"sig","kid":"B4F7C5533A06B22E6D349BEFD84B76E730161B55","x5t":"tPfFUzoGsi5tNJvv2Et25zAWG1U","e":"AQAB","n":"zDXSeNo4oO-Tn372eKUywF40D0HG4XXeYtbYtdnpVsIZkDDouZr2jFeq3C-AUb546CJXFqqZj6YZPOMtiHBfzyDGThd45mQvNwQ18B7lae4vab1hvxx9HZGku64Wy5JlqT2jHJ-WR7GS9OZjHSeioMoDE654LhDxJthfj_C2G0jA_RTnPQKnQgciv5JiENTUwrghr9cXzBNgPE0QLAhKrCEoVoSxYOWTL9EBCUc2DB2Vah7RHNfNItrXbrdqvrDQ5rXBH8Rq6irjSF_FjcuIwMkTmLOkswnC_qBN7qjbmgLRIxG3YiSnZR5bgyhjFWNzea0jmuWEiFIIIMwTfPXpPw","x5c":["MIID8TCCAtmgAwIBAgIJAIRTKytMROvuMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYDVQQGEwJ6aDERMA8GA1UECAwIY2hpbmEICAgxETAPBgNVBAcMCHNoYW5naGFpMREwDwYDVQQKDAhob21laW5uczERMA8GA1UECwwIaG9tZWlubnMxDzANBgNVBAMMBmlydmluZzEiMCAGCSqGSIb3DQEJARYTeXR6aG91QGhvbWVpbm5zLmNvbTAeFw0xODA3MjUwNzI3MzZaFw0xOTA3MjUwNzI3MzZaMIGOMQswCQYDVQQGEwJ6aDERMA8GA1UECAwIY2hpbmEICAgxETAPBgNVBAcMCHNoYW5naGFpMREwDwYDVQQKDAhob21laW5uczERMA8GA1UECwwIaG9tZWlubnMxDzANBgNVBAMMBmlydmluZzEiMCAGCSqGSIb3DQEJARYTeXR6aG91QGhvbWVpbm5zLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMw10njaOKDvk59+9nilMsBeNA9BxuF13mLW2LXZ6VbCGZAw6Lma9oxXqtwvgFG+eOgiVxaqmY+mGTzjLYhwX88gxk4XeOZkLzcENfAe5WnuL2m9Yb8cfR2RpLuuFsuSZak9oxyflkexkvTmYx0noqDKAxOueC4Q8SbYX4/wthtIwP0U5z0Cp0IHIr+SYhDU1MK4Ia/XF8wTYDxNECwISqwhKFaEsWDlky/RAQlHNgwdlWoe0RzXzSLa1263ar6w0Oa1wR/Eauoq40hfxY3LiMDJE5izpLMJwv6gTe6o25oC0SMRt2Ikp2UeW4MoYxVjc3mtI5rlhIhSCCDME3z16T8CAwEAAaNQME4wHQYDVR0OBBYEFOK5Y2P7/L8KsOrPB+glPVkKi2VOMB8GA1UdIwQYMBaAFOK5Y2P7/L8KsOrPB+glPVkKi2VOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEnXXws/cBx5tA9cBfmkqGWzOU5/YmH9pzWchJ0ssggIqZVx0yd6ok7+C+2vKIRMp5E6GCfXWTB+LI7qjAVEvin1NwGZ06yNEsaYaJYMC/P/0TunoMEZmsLM3rk0aISbzkNciF+LVT16i0C+hT1+Pyr8lP4Ea1Uw0n50Np6SOwQ6e2PMFFOIaqjG94tuCN3RX819IJSQPbq9FtRmNvmbWPM1v2CO6SYT51SvsIHnZyn0rAK+h/hywVQqmI5ngi1nErIQEqybkZj00OhmYpAqsetWYU5Cs1qhJ70kktlrd+jMHdarVB9ko0h+ij6HL22mmBYAb7zVGWyDroNJVhEw6DA="],"alg":"RS256"}]}
client_credentials_reference_grant
static void Main(string[] args) { Task.Run(() => { return Run(); }); Console.ReadLine(); } public static async Task Run() { var disco = await DiscoveryClient.GetAsync("http://localhost:5000"); var client = new TokenClient(disco.TokenEndpoint, "client_credentials_reference_grant", "123456"); var response = await client.RequestClientCredentialsAsync("api"); if (response.IsError) { Console.WriteLine(response.Error); Console.Read(); } Console.WriteLine(response.Json); //call api var http = new HttpClient(); http.SetBearerToken(response.AccessToken); var message = await http.GetAsync("http://localhost:17181/api/values/1"); if (!message.IsSuccessStatusCode) { Console.WriteLine(message.ReasonPhrase); Console.Read(); } Console.WriteLine(message.Content.ReadAsStringAsync().Result); }
上述 AccessTokenType 定義爲 Reference ,調用 scope 爲 api 接口產生的報文以下:
得到 Token 的報文
POST http://localhost:5000/connect/token HTTP/1.1 Authorization: Basic Y2xpZW50X2NyZWRlbnRpYWxzX3JlZmVyZW5jZV9ncmFudDoxMjM0NTY= Accept: application/json Content-Type: application/x-www-form-urlencoded Content-Length: 39 Host: localhost:5000 grant_type=client_credentials&scope=api HTTP/1.1 200 OK Cache-Control: no-store, no-cache, max-age=0 Pragma: no-cache Content-Type: application/json; charset=UTF-8 Server: Kestrel X-SourceFiles: =?UTF-8?B?RDpcZ2l0aHViXFNlY3VyaW5nRm9yV2ViQVBJXElkU3J2NC5Ib3N0U3J2XGNvbm5lY3RcdG9rZW4=?= X-Powered-By: ASP.NET Date: Wed, 25 Jul 2018 11:12:12 GMT Content-Length: 123 {"access_token":"5ac6b2bf4779873d0b92af8f32d6a3a85937cf5821056c1f9ca5435d9e717007","expires_in":3600,"token_type":"Bearer"}
調用 API 產生的報文
GET http://localhost:17181/api/values/1 HTTP/1.1 Authorization: Bearer 5ac6b2bf4779873d0b92af8f32d6a3a85937cf5821056c1f9ca5435d9e717007 Host: localhost:17181 HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 Server: Kestrel X-SourceFiles: =?UTF-8?B?RDpcZ2l0aHViXFNlY3VyaW5nRm9yV2ViQVBJXElkU3J2NC5XZWJBcGlcYXBpXHZhbHVlc1wx?= X-Powered-By: ASP.NET Date: Wed, 25 Jul 2018 11:16:05 GMT Content-Length: 336 [{"type":"iss","value":"http://localhost:5000"},{"type":"nbf","value":"1532517133"},{"type":"exp","value":"1532520733"},{"type":"aud","value":"http://localhost:5000/resources"},{"type":"aud","value":"api"},{"type":"client_id","value":"client_credentials_reference_grant"},{"type":"active","value":"True"},{"type":"scope","value":"api"}] POST http://localhost:5000/connect/introspect HTTP/1.1 Accept: application/json Content-Type: application/x-www-form-urlencoded Content-Length: 135 Host: localhost:5000 token=5ac6b2bf4779873d0b92af8f32d6a3a85937cf5821056c1f9ca5435d9e717007&client_id=api&token_type_hint=access_token&client_secret=api_pwd HTTP/1.1 200 OK Cache-Control: no-store, no-cache, max-age=0 Pragma: no-cache Content-Type: application/json; charset=UTF-8 Server: Kestrel X-SourceFiles: =?UTF-8?B?RDpcZ2l0aHViXFNlY3VyaW5nRm9yV2ViQVBJXElkU3J2NC5Ib3N0U3J2XGNvbm5lY3RcaW50cm9zcGVjdA==?= X-Powered-By: ASP.NET Date: Wed, 25 Jul 2018 11:16:05 GMT Content-Length: 190 {"iss":"http://localhost:5000","nbf":1532517133,"exp":1532520733,"aud":["http://localhost:5000/resources","api"],"client_id":"client_credentials_reference_grant","active":true,"scope":"api"}
當 AccessTokenType 定義爲 Reference 的時候,驗證資源端要注意配置 ApiSecrets 以確保 POST /connect/introspect HTTP/1.1 接口能驗證經過,當 AccessTokenType 定義爲 Jwt 的時候則資源端可不配置 options.ApiSecret 選項。
//jwt //services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) // .AddIdentityServerAuthentication(options => // { // options.Authority = "http://localhost:5000"; // options.RequireHttpsMetadata = false; // options.ApiName = "api"; // }); //Enable reference tokens services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.ApiName = "api"; options.ApiSecret = "api_pwd"; });
簽名證書
OpenSSL 是不支持 window 的,能夠在 Linux 上經過以下命令生成
openssl req -newkey rsa:2048 -nodes -keyout idsrv4.key -x509 -days 365 -out idsrv4.cer openssl pkcs12 -export -in idsrv4.cer -inkey idsrv4.key -out idsrv4.pfx
完成後會有三個文件(選中配置文件設置始終複製)
root@iZuf60cj5pna5im3va46nlZ:~# tree
.
├── idsrv4.cer
├── idsrv4.key
└── idsrv4.pfx
配置文件
{ "Certs": { "Path": "Certs\\idsrv4.pfx", "Pwd": "123456" }, "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*" }
最後訪問 /.well-known/openid-configuration/jwks 確認
{ "keys": [ { "kty": "RSA", "use": "sig", "kid": "B4F7C5533A06B22E6D349BEFD84B76E730161B55", "x5t": "tPfFUzoGsi5tNJvv2Et25zAWG1U", "e": "AQAB", "n": "zDXSeNo4oO-Tn372eKUywF40D0HG4XXeYtbYtdnpVsIZkDDouZr2jFeq3C-AUb546CJXFqqZj6YZPOMtiHBfzyDGThd45mQvNwQ18B7lae4vab1hvxx9HZGku64Wy5JlqT2jHJ-WR7GS9OZjHSeioMoDE654LhDxJthfj_C2G0jA_RTnPQKnQgciv5JiENTUwrghr9cXzBNgPE0QLAhKrCEoVoSxYOWTL9EBCUc2DB2Vah7RHNfNItrXbrdqvrDQ5rXBH8Rq6irjSF_FjcuIwMkTmLOkswnC_qBN7qjbmgLRIxG3YiSnZR5bgyhjFWNzea0jmuWEiFIIIMwTfPXpPw", "x5c": [ "MIID8TCCAtmgAwIBAgIJAIRTKytMROvuMA0GCSqGSIb3DQEBCwUAMIGOMQswCQYDVQQGEwJ6aDERMA8GA1UECAwIY2hpbmEICAgxETAPBgNVBAcMCHNoYW5naGFpMREwDwYDVQQKDAhob21laW5uczERMA8GA1UECwwIaG9tZWlubnMxDzANBgNVBAMMBmlydmluZzEiMCAGCSqGSIb3DQEJARYTeXR6aG91QGhvbWVpbm5zLmNvbTAeFw0xODA3MjUwNzI3MzZaFw0xOTA3MjUwNzI3MzZaMIGOMQswCQYDVQQGEwJ6aDERMA8GA1UECAwIY2hpbmEICAgxETAPBgNVBAcMCHNoYW5naGFpMREwDwYDVQQKDAhob21laW5uczERMA8GA1UECwwIaG9tZWlubnMxDzANBgNVBAMMBmlydmluZzEiMCAGCSqGSIb3DQEJARYTeXR6aG91QGhvbWVpbm5zLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMw10njaOKDvk59+9nilMsBeNA9BxuF13mLW2LXZ6VbCGZAw6Lma9oxXqtwvgFG+eOgiVxaqmY+mGTzjLYhwX88gxk4XeOZkLzcENfAe5WnuL2m9Yb8cfR2RpLuuFsuSZak9oxyflkexkvTmYx0noqDKAxOueC4Q8SbYX4/wthtIwP0U5z0Cp0IHIr+SYhDU1MK4Ia/XF8wTYDxNECwISqwhKFaEsWDlky/RAQlHNgwdlWoe0RzXzSLa1263ar6w0Oa1wR/Eauoq40hfxY3LiMDJE5izpLMJwv6gTe6o25oC0SMRt2Ikp2UeW4MoYxVjc3mtI5rlhIhSCCDME3z16T8CAwEAAaNQME4wHQYDVR0OBBYEFOK5Y2P7/L8KsOrPB+glPVkKi2VOMB8GA1UdIwQYMBaAFOK5Y2P7/L8KsOrPB+glPVkKi2VOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEnXXws/cBx5tA9cBfmkqGWzOU5/YmH9pzWchJ0ssggIqZVx0yd6ok7+C+2vKIRMp5E6GCfXWTB+LI7qjAVEvin1NwGZ06yNEsaYaJYMC/P/0TunoMEZmsLM3rk0aISbzkNciF+LVT16i0C+hT1+Pyr8lP4Ea1Uw0n50Np6SOwQ6e2PMFFOIaqjG94tuCN3RX819IJSQPbq9FtRmNvmbWPM1v2CO6SYT51SvsIHnZyn0rAK+h/hywVQqmI5ngi1nErIQEqybkZj00OhmYpAqsetWYU5Cs1qhJ70kktlrd+jMHdarVB9ko0h+ij6HL22mmBYAb7zVGWyDroNJVhEw6DA=" ], "alg": "RS256" } ] }
IdentityServer QuickStart-UI 問題
默認 IdentityServer4 是沒有登陸受權相關頁面的,能夠在 https://github.com/IdentityServer/IdentityServer4.Quickstart.UI/tree/release 下載後複製到咱們的項目中便可。
Token 的類型
默認 Token 的類型有 Jwt 與 Reference 。
Self-contained Json Web Token(默認使用 RFC 7519 - JSON Web Token (JWT))
API 端(資源服務器)收到第一個請求後(僅第一次請求,後續的請求都使用第一次得到的公鑰進行驗證),會去 IdentityServer 服務端調用/.well-known/openid-configuration/jwks 接口獲取 RSA 公鑰驗籤以確認 token 是否合法。另外使用 JWT 的方式 token 是不可撤銷的(POST/connect/revocation)。
好比上述得到 token 能夠在 https://jwt.io 網站中查看 JWT 組成
Reference token
API 端(資源服務器)須要每次去訪問 IdentityServer 的 token 驗證接口(POST /connect/introspect),固然 API 端也能夠配置必定的時間來緩存結果,以減小驗證的頻率。
重寫的接口
通常會重寫 IResourceOwnerPasswordValidator 與 IProfileService 接口來驗證用戶與自定義一些功能。
數據的持久化
https://identityserver4.readthedocs.io/en/release/reference/ef.html
http://blog.stoverud.no/posts/identity-server-with-mongodb/
REFER:
https://github.com/IdentityServer/IdentityServer4
http://docs.identityserver.io/en/release/index.html
https://github.com/Microsoft/api-guidelines
https://github.com/dotnet-architecture/eShopOnContainers
https://leastprivilege.com/2016/01/17/which-openid-connectoauth-2-o-flow-is-the-right-one/
https://identityserver4.readthedocs.io/en/release/reference/ef.html
http://blog.stoverud.no/posts/identity-server-with-mongodb/
https://github.com/ddrsql/IdentityServer4.Adminhttps://github.com/skoruba/IdentityServer4.Admin
IdentityServer 4 has changed and replaced IUserService with IResourceOwnerPasswordValidator and IProfileService
https://stackoverflow.com/questions/35304038/identityserver4-register-userservice-and-get-users-from-database-in-asp-net-core
http://www.cnblogs.com/skig/p/6079457.html
http://www.cnblogs.com/xishuai/p/identityserver4-slb.html
http://www.tugberkugurlu.com/archive/asp-net-core-authentication-in-a-load-balanced-environment-with-haproxy-and-redis
https://github.com/jessetalk/aspnet-core-in-practise/blob/master/chapter1.md
http://www.cnblogs.com/RainingNight/p/oidc-authentication-in-asp-net-core.html
https://media.readthedocs.org/pdf/identityserver4/release/identityserver4.pdf
https://github.com/ory/hydra
http://www.cnblogs.com/skig/p/AspNetCoreAuthCode.htmlhttps://github.com/nginxinc/nginx-openid-connect