1 private static async Task Main() 2 { 3 // 其餘代碼... 4 var client = new HttpClient(); 5 // 沒啥說的,OIDC協議規範定義的端點發現機制,固然你也能夠繞過這步,感興趣的能夠看看這個方法的實現 6 var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000"); 7 8 // 經過clientid secret tokenendpoint等信息獲取accesstoken訪問令牌 9 var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest 10 { 11 Address = disco.TokenEndpoint, 12 ClientId = "client", 13 ClientSecret = "secret", 14 Scope = "api1" 15 }); 16 Console.WriteLine(tokenResponse.Json); 17 // 經過accesstoken 調用 受保護的api 18 var apiClient = new HttpClient(); 19 apiClient.SetBearerToken(tokenResponse.AccessToken); 20 var response = await apiClient.GetAsync("http://localhost:5001/identity"); 21 if (!response.IsSuccessStatusCode) 22 { 23 Console.WriteLine(response.StatusCode); 24 } 25 else 26 { 27 var content = await response.Content.ReadAsStringAsync(); 28 Console.WriteLine(JArray.Parse(content)); 29 } 30 }
1 // 注入服務 2 public void ConfigureServices(IServiceCollection services) 3 { 4 // 注入IDS4的基礎服務 5 var builder = services.AddIdentityServer() 6 // 注入resource資源到內存,該資源咱們能夠理解爲ids4本地資源,好比用戶信息等 7 .AddInMemoryIdentityResources(Config.GetIdentityResources()) 8 // 注入api資源,也就是那些受保護的api 9 .AddInMemoryApiResources(Config.GetApis()) 10 // 注入client客戶端資源 11 .AddInMemoryClients(Config.GetClients()); 12 if (Environment.IsDevelopment()) 13 { 14 // 臨時開發環境下的證書 15 builder.AddDeveloperSigningCredential(); 16 } 17 } 18 // 注入中間件 19 public void Configure(IApplicationBuilder app) 20 { 21 // 註冊ids4中間件 22 app.UseIdentityServer(); 23 }
1 public static class Config 2 { 3 public static IEnumerable<IdentityResource> GetIdentityResources() 4 { 5 return new IdentityResource[] 6 { 7 new IdentityResources.OpenId() 8 }; 9 } 10 public static IEnumerable<ApiResource> GetApis() 11 { 12 return new List<ApiResource> 13 { 14 // 注意api1這個名稱,須要跟api資源Audience的名稱同樣,包括下邊的scope裏面的名稱 15 new ApiResource("api1", "My API") 16 }; 17 } 18 public static IEnumerable<Client> GetClients() 19 { 20 return new List<Client> 21 { 22 new Client 23 { 24 // 客戶端id 25 ClientId = "client", 26 // 受權類型 27 AllowedGrantTypes = GrantTypes.ClientCredentials, 28 // secret 29 ClientSecrets = 30 { 31 new Secret("secret".Sha256()) 32 }, 33 // scope受權範圍 34 AllowedScopes = { "api1" } 35 } 36 }; 37 } 38 }
1 public void ConfigureServices(IServiceCollection services) 2 { 3 // 注入mvc的核心服務 4 services.AddMvcCore() 5 // 注入aspnetcore認證服務 6 .AddAuthorization() 7 .AddJsonFormatters(); 8 // 注入jwtbearer基礎服務 9 services.AddAuthentication("Bearer") 10 .AddJwtBearer("Bearer", options => 11 { 12 // ids4地址,用於驗證accesstoken 13 options.Authority = "http://localhost:5000"; 14 // 非必須使用https 15 options.RequireHttpsMetadata = false; 16 // api資源名稱 17 options.Audience = "api1"; 18 }); 19 } 20 public void Configure(IApplicationBuilder app) 21 { 22 // 注入aspnetcore認證中間件 23 app.UseAuthentication(); 24 app.UseMvc(); 25 } 26 27 [Route("identity")] 28 [Authorize] // 須要認證應用攜帶正確的accesstoken才能訪問 29 public class IdentityController : ControllerBase 30 { 31 public IActionResult Get() 32 { 33 return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); 34 } 35 }
Request: POST /token HTTP/1.1 Host: authorization-server.com grant_type=client_credentials // 必須 &client_id=xxxxxxxxxx // 必須 &client_secret=xxxxxxxxxx // 必須
1 HTTP/1.1 200 OK 2 Content-Type: application/json 3 Cache-Control: no-store 4 Pragma: no-cache 5 { 6 "access_token":"MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3", // 必須 7 "token_type":"bearer", // 必須 8 "expires_in":3600, // 9 "refresh_token":"IwOGYzYTlmM2YxOTQ5MGE3YmNmMDFkNTVk", 10 "scope":"create" 11 }
1 public void ConfigureServices(IServiceCollection services) 2 { 3 services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1); 4 // 其餘代碼... 5 // 注入ids4基礎服務 6 var builder = services.AddIdentityServer(options => 7 { 8 options.Events.RaiseErrorEvents = true; 9 options.Events.RaiseInformationEvents = true; 10 options.Events.RaiseFailureEvents = true; 11 options.Events.RaiseSuccessEvents = true; 12 }) 13 // 測試用戶 14 .AddTestUsers(TestUsers.Users); 15 // 這些個相似第一個實例 16 builder.AddInMemoryIdentityResources(Config.GetIdentityResources()); 17 builder.AddInMemoryApiResources(Config.GetApis()); 18 builder.AddInMemoryClients(Config.GetClients()); 19 // 臨時開發證書,生成環境建議使用機構頒發的證書 20 if (Environment.IsDevelopment()) 21 { 22 builder.AddDeveloperSigningCredential(); 23 } 24 } 25 public void Configure(IApplicationBuilder app) 26 { 27 if (Environment.IsDevelopment()) 28 { 29 app.UseDeveloperExceptionPage(); 30 } 31 // 註冊ids4中間件 32 app.UseIdentityServer(); 33 app.UseStaticFiles(); 34 app.UseMvcWithDefaultRoute(); 35 }
1 public static IEnumerable<IdentityResource> GetIdentityResources() 2 { 3 // 身份資源,相似上一個實例 4 return new IdentityResource[] 5 { 6 new IdentityResources.OpenId(), 7 new IdentityResources.Profile(), 8 new IdentityResources.Address(), 9 new IdentityResources.Phone(), 10 new IdentityResources.Email(), 11 // 策略資源,也是身份資源的一種吧 12 new IdentityResource("names", "姓名", new List<string> { "FamilyName" }), 13 }; 14 } 15 public static IEnumerable<ApiResource> GetApis() 16 { 17 return new ApiResource[] 18 { 19 new ApiResource("api1", "testapi", new List<string> { "FamilyName" }) 20 { 21 // 這個secrets主要是用於api在ids4上的驗證,由於須要referencetoken刷新accesstoken 22 ApiSecrets = { new Secret("api1 secret".Sha256()) } 23 } 24 }; 25 } 26 public static IEnumerable<Client> GetClients() 27 { 28 return new[] 29 { 30 new Client 31 { 32 ClientId = "hybrid client mvc", 33 ClientSecrets = {new Secret("hybrid secret".Sha256()) }, 34 AllowedGrantTypes = GrantTypes.Hybrid, // 混合認證流程,OIDC協議 35 AccessTokenType = AccessTokenType.Jwt, // accesstoken類型jwt,還有referencetoken類型 36 AllowAccessTokensViaBrowser = true, 37 AccessTokenLifetime = 60, // 時間單位是秒,表示accesstoken60秒後過時 38 RedirectUris = // 跳轉登陸地址 39 { 40 "http://localhost:5002/signin-oidc" 41 }, 42 PostLogoutRedirectUris = // 跳轉登出地址 43 { 44 "http://localhost:5002/signout-callback-oidc" 45 }, 46 AllowOfflineAccess = true, 47 AlwaysIncludeUserClaimsInIdToken = true, 48 AllowedScopes = 49 { 50 "api1", 51 IdentityServerConstants.StandardScopes.OpenId, 52 IdentityServerConstants.StandardScopes.Email, 53 IdentityServerConstants.StandardScopes.Address, 54 IdentityServerConstants.StandardScopes.Phone, 55 IdentityServerConstants.StandardScopes.Profile, 56 "names" // 策略scope 57 } 58 } 59 }; 60 } 61 62 // 測試用戶 63 public static List<TestUser> Users = new List<TestUser> 64 { 65 new TestUser{ 66 SubjectId = "1", Username = "test", Password = "test", 67 Claims = 68 { 69 new Claim(JwtClaimTypes.Name, "test"), 70 new Claim(JwtClaimTypes.FamilyName, "test"), 71 new Claim(JwtClaimTypes.Email, "test@email.com"), 72 new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 73 new Claim(JwtClaimTypes.Address, @"{ '街道': '萬家麗', '地址': '長沙','country': '大中國' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json), 74 } 75 }, 76 new TestUser{ 77 SubjectId = "2", Username = "demo", Password = "demo", 78 Claims = 79 { 80 new Claim(JwtClaimTypes.Name, "demo"), 81 new Claim(JwtClaimTypes.FamilyName, "demo"), 82 new Claim(JwtClaimTypes.Email, "demo@email.com"), 83 new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), 84 new Claim(JwtClaimTypes.Address, @"{ '街道': '五一廣場', '地址': '長沙','country': '大中國' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json), 85 new Claim("name", "FamilyName"), // 受權策略claim 86 } 87 } 88 }; 89 }
1 public void ConfigureServices(IServiceCollection services) 2 { 3 // 其餘代碼... 4 services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 5 JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); // 清空jwt用戶信息的映射關係。 6 services.AddAuthentication(options => 7 { 8 // 註冊cookiesScheme,表示本地認證採用cookie認證 9 options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; 10 // 註冊oidcscheme,表示遠程認證採用oidc認證 11 options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; 12 }) 13 // 注入cookie認證處理器, 14 .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => 15 { 16 options.AccessDeniedPath = "/Unauthorized/AccessDenied"; // 指定未受權的頁面路由地址 17 }) 18 // 注入oidc認證處理器 19 .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => 20 { 21 // 認證成功登陸到本地cookie,主要是解決本地認證的問題。 22 options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; 23 options.Authority = "http://localhost:5000"; // ids4 base地址 24 options.RequireHttpsMetadata = false; 25 options.ClientId = "hybrid client mvc"; 26 options.ClientSecret = "hybrid secret"; 27 options.SaveTokens = true; // 保存token到store 28 options.ResponseType = "code id_token token"; // responsetype有三種組合,這裏使用的是組合最多的一種,能夠看前面一張圖 29 options.Scope.Clear(); 30 options.Scope.Add("api1"); 31 options.Scope.Add(OidcConstants.StandardScopes.OpenId); 32 options.Scope.Add(OidcConstants.StandardScopes.Profile); 33 options.Scope.Add(OidcConstants.StandardScopes.Email); 34 options.Scope.Add(OidcConstants.StandardScopes.Phone); 35 options.Scope.Add(OidcConstants.StandardScopes.Address); 36 options.Scope.Add("names"); // ids4那邊定義的策略名稱 37 options.Scope.Add(OidcConstants.StandardScopes.OfflineAccess); 38 }); 39 services.AddAuthorization(options => 40 { 41 options.AddPolicy("FamilyNamePolicy", builder => // 注入自定義策略 42 { 43 builder.AddRequirements(new NameRequirement()); 44 }); 45 }); 46 // 注入策略處理器 47 services.AddSingleton<IAuthorizationHandler, NameHandler>(); 48 } 49 public void Configure(IApplicationBuilder app, IHostingEnvironment env) 50 { 51 if (env.IsDevelopment()) 52 { 53 app.UseDeveloperExceptionPage(); 54 } 55 else 56 { 57 app.UseExceptionHandler("/Home/Error"); 58 } 59 app.UseStaticFiles(); 60 // 注入認證中間件 61 app.UseAuthentication(); 62 app.UseCookiePolicy(); 63 app.UseMvc(routes => 64 { 65 routes.MapRoute( 66 name: "default", 67 template: "{controller=Home}/{action=Index}/{id?}"); 68 }); 69 }
1 // homecontroller 2 public class HomeController : Controller 3 { 4 // 其餘成員 5 6 public IActionResult Index() 7 { 8 var user = User.Identity; 9 return View(); 10 } 11 // 沒什麼特別的,其實就是aspnetcore內置那套策略機制 12 [Authorize(Policy = "FamilyNamePolicy")] 13 public IActionResult Privacy() 14 { 15 return View(); 16 } 17 } 18 19 // 自定義策略規則 20 public class NameHandler : AuthorizationHandler<NameRequirement > 21 { 22 // 自定義策略處理器 23 protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, 24 NameRequirement requirement) 25 { 26 var familyName = context.User.Claims.FirstOrDefault(c => c.Type == JwtClaimTypes.FamilyName)?.Value; 27 if (familyName == "demo" && context.User.Identity.IsAuthenticated) // 規則爲claim familyname爲demo&爲登陸用戶 28 { 29 context.Succeed(requirement); 30 return Task.CompletedTask; 31 } 32 context.Fail(); 33 return Task.CompletedTask; 34 } 35 }
1 // 刷新accesstoken 2 private async Task GetNewTokensAsync() 3 { 4 var client = new HttpClient(); 5 var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000"); 6 if (disco.IsError) 7 { } 8 var refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken); 9 // 獲取新的token,包含idtoken accesstoken referencetoken等信息 10 var tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest 11 { 12 Address = disco.TokenEndpoint, 13 ClientId = "hybrid client mvc", 14 ClientSecret = "hybrid secret", 15 Scope = "api1 openid profile email phone address names", 16 GrantType = OpenIdConnectGrantTypes.RefreshToken, 17 RefreshToken = refreshToken 18 }); 19 if (tokenResponse.IsError) 20 { } 21 // 刷新過時時間 22 var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResponse.ExpiresIn); 23 var tokens = new[] 24 { 25 new AuthenticationToken 26 { 27 Name = OpenIdConnectParameterNames.IdToken, 28 Value = tokenResponse.IdentityToken 29 }, 30 new AuthenticationToken 31 { 32 Name = OpenIdConnectParameterNames.AccessToken, 33 Value = tokenResponse.AccessToken 34 }, 35 new AuthenticationToken 36 { 37 Name = OpenIdConnectParameterNames.RefreshToken, 38 Value = tokenResponse.RefreshToken 39 }, 40 new AuthenticationToken 41 { 42 Name = "expires_at", 43 Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) 44 } 45 }; 46 // 本地從新cookie認證 47 var currentAuthenticateResult = 48 await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); 49 // 本地存儲token 50 currentAuthenticateResult.Properties.StoreTokens(tokens); 51 // 從新登陸 52 await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, 53 currentAuthenticateResult.Principal, currentAuthenticateResult.Properties); 54 return; 55 }
1 // startup啓動類 2 public void ConfigureServices(IServiceCollection services) 3 { 4 services.AddMvc(); 5 services.AddMvcCore() 6 .AddAuthorization() 7 .AddJsonFormatters(); 8 services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) 9 .AddIdentityServerAuthentication(options => // 以前這個地方是jwtbeare,我猜只是ids4的擴展方法對jwtbeare換了個馬甲,後續源碼分析會介紹 10 { 11 options.Authority = "http://localhost:5000"; // ids4 base基地址 12 options.ApiName = "api1"; // 刷新token須要提供api名稱 13 options.RequireHttpsMetadata = false; 14 options.ApiSecret = "api1 secret"; // 須要刷新token ids4須要提供secret 15 options.JwtValidationClockSkew = TimeSpan.FromMinutes(1); // 檢查時間,也就是api到ids4token驗證時間間隔 16 }); 17 services.AddMemoryCache(); 18 } 19 public void Configure(IApplicationBuilder app, IHostingEnvironment env) 20 { 21 // 註冊中間件 22 app.UseAuthentication(); 23 app.UseMvc(); 24 }
1 public class IdentityServerMiddleware 2 { 3 // 其餘成員... 4 public IdentityServerMiddleware(RequestDelegate next, ILogger<IdentityServerMiddleware> logger) 5 { 6 _next = next; 7 _logger = logger; 8 } 9 10 public async Task Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events) 11 { 12 // 從本地cookie讀取認證信息,若是以認證直接設置請求上下文 13 await session.EnsureSessionIdCookieAsync(); 14 try 15 { 16 // 這個地方咱們把它理解爲請求路由查找對應的endpoint端點,有點相似攔截,由於若是匹配到請求地址,endpoint端點處理完以後直接response 17 var endpoint = router.Find(context); 18 if (endpoint != null) 19 { 20 // 若是匹配到由端點處理,好比咱們上面介紹的tokenendpoint在處理完請求以後直接經過executeasync把accesstoken等信息直接寫入response 21 var result = await endpoint.ProcessAsync(context); 22 if (result != null) 23 { 24 // 寫入response 25 await result.ExecuteAsync(context); 26 } 27 28 return; 29 } 30 } 31 catch (Exception ex) 32 { 33 await events.RaiseAsync(new UnhandledExceptionEvent(ex)); 34 _logger.LogCritical(ex, "Unhandled exception: {exception}", ex.Message); 35 throw; 36 } 37 38 // 若是未被路由端點匹配,傳遞到下一個中間件。 39 await _next(context); 40 } 41 }
1 // 注入平臺默認的端點 2 public static IIdentityServerBuilder AddDefaultEndpoints(this IIdentityServerBuilder builder) 3 { 4 builder.Services.AddTransient<IEndpointRouter, EndpointRouter>(); 5 builder.AddEndpoint<AuthorizeCallbackEndpoint>(EndpointNames.Authorize, ProtocolRoutePaths.AuthorizeCallback.EnsureLeadingSlash()); 6 builder.AddEndpoint<AuthorizeEndpoint>(EndpointNames.Authorize, ProtocolRoutePaths.Authorize.EnsureLeadingSlash()); 7 builder.AddEndpoint<CheckSessionEndpoint>(EndpointNames.CheckSession, ProtocolRoutePaths.CheckSession.EnsureLeadingSlash()); 8 builder.AddEndpoint<DeviceAuthorizationEndpoint>(EndpointNames.DeviceAuthorization, ProtocolRoutePaths.DeviceAuthorization.EnsureLeadingSlash()); 9 builder.AddEndpoint<DiscoveryKeyEndpoint>(EndpointNames.Discovery, ProtocolRoutePaths.DiscoveryWebKeys.EnsureLeadingSlash()); 10 builder.AddEndpoint<DiscoveryEndpoint>(EndpointNames.Discovery, ProtocolRoutePaths.DiscoveryConfiguration.EnsureLeadingSlash()); 11 builder.AddEndpoint<EndSessionCallbackEndpoint>(EndpointNames.EndSession, ProtocolRoutePaths.EndSessionCallback.EnsureLeadingSlash()); 12 builder.AddEndpoint<EndSessionEndpoint>(EndpointNames.EndSession, ProtocolRoutePaths.EndSession.EnsureLeadingSlash()); 13 builder.AddEndpoint<IntrospectionEndpoint>(EndpointNames.Introspection, ProtocolRoutePaths.Introspection.EnsureLeadingSlash()); 14 builder.AddEndpoint<TokenRevocationEndpoint>(EndpointNames.Revocation, ProtocolRoutePaths.Revocation.EnsureLeadingSlash()); 15 builder.AddEndpoint<TokenEndpoint>(EndpointNames.Token, ProtocolRoutePaths.Token.EnsureLeadingSlash()); 16 builder.AddEndpoint<UserInfoEndpoint>(EndpointNames.UserInfo, ProtocolRoutePaths.UserInfo.EnsureLeadingSlash()); 17 return builder; 18 }
1 internal class TokenEndpoint : IEndpointHandler 2 { 3 private readonly IClientSecretValidator _clientValidator; 4 private readonly ITokenRequestValidator _requestValidator; 5 private readonly ITokenResponseGenerator _responseGenerator; 6 private readonly IEventService _events; 7 private readonly ILogger _logger; 8 9 // 其餘成員... 10 11 public TokenEndpoint( 12 IClientSecretValidator clientValidator, 13 ITokenRequestValidator requestValidator, 14 ITokenResponseGenerator responseGenerator, 15 IEventService events, 16 ILogger<TokenEndpoint> logger) 17 { 18 _clientValidator = clientValidator; 19 _requestValidator = requestValidator; 20 _responseGenerator = responseGenerator; 21 _events = events; 22 _logger = logger; 23 } 24 25 // tokenendpoint端點處理請求入口方法 26 public async Task<IEndpointResult> ProcessAsync(HttpContext context) 27 { 28 29 // 首先驗證http操做,非post 表單提交直接返回錯誤 30 if (!HttpMethods.IsPost(context.Request.Method) || !context.Request.HasFormContentType) 31 { 32 _logger.LogWarning("Invalid HTTP request for token endpoint"); 33 return Error(OidcConstants.TokenErrors.InvalidRequest); 34 } 35 // 進一步驗證 36 return await ProcessTokenRequestAsync(context); 37 } 38 39 private async Task<IEndpointResult> ProcessTokenRequestAsync(HttpContext context) 40 { 41 // 首先驗證client 就是oauth2協議裏邊定義的clientid secret等 42 var clientResult = await _clientValidator.ValidateAsync(context); 43 if (clientResult.Client == null) // 若是驗證未經過直接返回invalidclient 44 { 45 return Error(OidcConstants.TokenErrors.InvalidClient); 46 } 47 var form = (await context.Request.ReadFormAsync()).AsNameValueCollection(); 48 _logger.LogTrace("Calling into token request validator: {type}", _requestValidator.GetType().FullName); 49 // 而後驗證請求參數,這些參數能夠查看OAuth2,根據不一樣的GrantType啓動不一樣的驗證規則,而且生成AuthorizationCode 50 var requestResult = await _requestValidator.ValidateRequestAsync(form, clientResult); 51 if (requestResult.IsError) 52 { 53 await _events.RaiseAsync(new TokenIssuedFailureEvent(requestResult)); 54 return Error(requestResult.Error, requestResult.ErrorDescription, requestResult.CustomResponse); 55 } 56 // create response 57 _logger.LogTrace("Calling into token request response generator: {type}", _responseGenerator.GetType().FullName); 58 // 生成accesstoken,若是有須要還會生成refencetoken id token等 59 var response = await _responseGenerator.ProcessAsync(requestResult); 60 await _events.RaiseAsync(new TokenIssuedSuccessEvent(response, requestResult)); 61 LogTokens(response, requestResult); 62 // return result 63 _logger.LogDebug("Token request success."); 64 // 返回 65 return new TokenResult(response); 66 } 67 }
1 public interface IClientSecretValidator 2 { 3 // 驗證 4 Task<ClientSecretValidationResult> ValidateAsync(HttpContext context); 5 }
1 public class ClientSecretValidator : IClientSecretValidator 2 { 3 // 其餘成員... 4 public ClientSecretValidator(IClientStore clients, SecretParser parser, SecretValidator validator, IEventService events, ILogger<ClientSecretValidator> logger) 5 { 6 _clients = clients; 7 _parser = parser; 8 _validator = validator; 9 _events = events; 10 _logger = logger; 11 } 12 // client驗證明現 13 public async Task<ClientSecretValidationResult> ValidateAsync(HttpContext context) 14 { 15 _logger.LogDebug("Start client validation"); 16 var fail = new ClientSecretValidationResult 17 { 18 IsError = true 19 }; 20 // 默認預設4種parser,這裏爲PostBodySecretParser,驗證context form是否包含clien secret 21 var parsedSecret = await _parser.ParseAsync(context); 22 if (parsedSecret == null) 23 { 24 await RaiseFailureEventAsync("unknown", "No client id found"); 25 _logger.LogError("No client identifier found"); 26 return fail; 27 } 28 // 從內存或者其餘存儲介質經過id獲取client,默認是內存裏面加載,若是未重寫資源存儲 29 var client = await _clients.FindEnabledClientByIdAsync(parsedSecret.Id); 30 if (client == null) 31 { 32 await RaiseFailureEventAsync(parsedSecret.Id, "Unknown client"); 33 _logger.LogError("No client with id '{clientId}' found. aborting", parsedSecret.Id); 34 return fail; 35 } 36 SecretValidationResult secretValidationResult = null; 37 if (!client.RequireClientSecret || client.IsImplicitOnly()) 38 { 39 _logger.LogDebug("Public Client - skipping secret validation success"); 40 } 41 else 42 { 43 secretValidationResult = await _validator.ValidateAsync(parsedSecret, client.ClientSecrets); 44 if (secretValidationResult.Success == false) 45 { 46 await RaiseFailureEventAsync(client.ClientId, "Invalid client secret"); 47 _logger.LogError("Client secret validation failed for client: {clientId}.", client.ClientId); 48 return fail; 49 } 50 } 51 _logger.LogDebug("Client validation success"); 52 // 返回clientresult這麼一個對象 53 var success = new ClientSecretValidationResult 54 { 55 IsError = false, 56 Client = client, 57 Secret = parsedSecret, 58 Confirmation = secretValidationResult?.Confirmation 59 }; 60 await RaiseSuccessEventAsync(client.ClientId, parsedSecret.Type); 61 return success; 62 } 63 }
1 public interface ITokenRequestValidator 2 { 3 4 Task<TokenRequestValidationResult> ValidateRequestAsync(NameValueCollection parameters, ClientSecretValidationResult clientValidationResult); 5 }
1 internal class TokenRequestValidator : ITokenRequestValidator 2 { 3 // 其餘代碼 4 public async Task<TokenRequestValidationResult> ValidateRequestAsync(NameValueCollection parameters, ClientSecretValidationResult clientValidationResult) 5 { 6 7 _validatedRequest = new ValidatedTokenRequest 8 { 9 Raw = parameters ?? throw new ArgumentNullException(nameof(parameters)), 10 Options = _options 11 }; 12 13 if (clientValidationResult == null) throw new ArgumentNullException(nameof(clientValidationResult)); 14 15 _validatedRequest.SetClient(clientValidationResult.Client, clientValidationResult.Secret, clientValidationResult.Confirmation); 16 // 驗證protocoltype是發oidc 17 if (_validatedRequest.Client.ProtocolType != IdentityServerConstants.ProtocolTypes.OpenIdConnect) 18 { 19 LogError("Invalid protocol type for client", 20 new 21 { 22 clientId = _validatedRequest.Client.ClientId, 23 expectedProtocolType = IdentityServerConstants.ProtocolTypes.OpenIdConnect, 24 actualProtocolType = _validatedRequest.Client.ProtocolType 25 }); 26 27 28 return Invalid(OidcConstants.TokenErrors.InvalidClient); 29 } 30 31 // grantType是否爲空 32 var grantType = parameters.Get(OidcConstants.TokenRequest.GrantType); 33 if (grantType.IsMissing()) 34 { 35 LogError("Grant type is missing"); 36 return Invalid(OidcConstants.TokenErrors.UnsupportedGrantType); 37 } 38 // 驗證granttype字符串長度 39 if (grantType.Length > _options.InputLengthRestrictions.GrantType) 40 { 41 LogError("Grant type is too long"); 42 return Invalid(OidcConstants.TokenErrors.UnsupportedGrantType); 43 } 44 45 _validatedRequest.GrantType = grantType; 46 // 根據不一樣的granttype,執行指定的驗證規則 47 switch (grantType) 48 { 49 case OidcConstants.GrantTypes.AuthorizationCode: 50 return await RunValidationAsync(ValidateAuthorizationCodeRequestAsync, parameters); 51 case OidcConstants.GrantTypes.ClientCredentials: 52 return await RunValidationAsync(ValidateClientCredentialsRequestAsync, parameters); 53 case OidcConstants.GrantTypes.Password: 54 return await RunValidationAsync(ValidateResourceOwnerCredentialRequestAsync, parameters); 55 case OidcConstants.GrantTypes.RefreshToken: 56 return await RunValidationAsync(ValidateRefreshTokenRequestAsync, parameters); 57 case OidcConstants.GrantTypes.DeviceCode: 58 return await RunValidationAsync(ValidateDeviceCodeRequestAsync, parameters); 59 default: 60 return await RunValidationAsync(ValidateExtensionGrantRequestAsync, parameters); 61 } 62 } 63 64 }
1 private async Task<TokenRequestValidationResult> RunValidationAsync(Func<NameValueCollection, Task<TokenRequestValidationResult>> validationFunc, NameValueCollection parameters) 2 { 3 // 仍是驗證,包括AllowedGrantTypes 生成code等等,具體就不貼代碼了。 4 var result = await validationFunc(parameters); 5 if (result.IsError) 6 { 7 return result; 8 } 9 10 // run custom validation 11 _logger.LogTrace("Calling into custom request validator: {type}", _customRequestValidator.GetType().FullName); 12 13 // 自定義驗證,驗證到天荒地老,還好默認沒有實現。 14 var customValidationContext = new CustomTokenRequestValidationContext { Result = result }; 15 await _customRequestValidator.ValidateAsync(customValidationContext); 16 17 if (customValidationContext.Result.IsError) 18 { 19 if (customValidationContext.Result.Error.IsPresent()) 20 { 21 LogError("Custom token request validator", new { error = customValidationContext.Result.Error }); 22 } 23 else 24 { 25 LogError("Custom token request validator error"); 26 } 27 28 return customValidationContext.Result; 29 } 30 31 LogSuccess(); 32 return customValidationContext.Result; 33 }
1 public class TokenRequestValidationResult : ValidationResult 2 { 3 // 其餘代碼... 4 // 這個屬性就包含了前面瘋狂驗證的那些參數 5 public ValidatedTokenRequest ValidatedRequest { get; } 6 7 public Dictionary<string, object> CustomResponse { get; set; } 8 }
1 public interface ITokenResponseGenerator 2 { 3 Task<TokenResponse> ProcessAsync(TokenRequestValidationResult validationResult); 4 }
1 public class TokenResponseGenerator : ITokenResponseGenerator 2 { 3 // 其餘代碼... 4 public virtual async Task<TokenResponse> ProcessAsync(TokenRequestValidationResult request) 5 { 6 switch (request.ValidatedRequest.GrantType) 7 { 8 case OidcConstants.GrantTypes.ClientCredentials: 9 return await ProcessClientCredentialsRequestAsync(request); 10 case OidcConstants.GrantTypes.Password: 11 return await ProcessPasswordRequestAsync(request); 12 case OidcConstants.GrantTypes.AuthorizationCode: 13 return await ProcessAuthorizationCodeRequestAsync(request); 14 case OidcConstants.GrantTypes.RefreshToken: 15 return await ProcessRefreshTokenRequestAsync(request); 16 case OidcConstants.GrantTypes.DeviceCode: 17 return await ProcessDeviceCodeRequestAsync(request); 18 default: 19 return await ProcessExtensionGrantRequestAsync(request); 20 } 21 } 22 }
1 protected virtual async Task<TokenResponse> ProcessAuthorizationCodeRequestAsync(TokenRequestValidationResult request) 2 { 3 // 生成accesstoken機制,時間比較晚了,生成代碼我就不貼了,我大概說下邏輯吧,jwt爲列,header部分不用說,ploay部分是Claim身份聲明信息,簽名部分由rsa私鑰作的簽名, 4 // refreshtoken 若是配置須要生成,則生成,配置要求我在實例裏面已經註釋了。 5 (var accessToken, var refreshToken) = await CreateAccessTokenAsync(request.ValidatedRequest); 6 var response = new TokenResponse 7 { 8 AccessToken = accessToken, 9 AccessTokenLifetime = request.ValidatedRequest.AccessTokenLifetime, 10 Custom = request.CustomResponse, 11 Scope = request.ValidatedRequest.AuthorizationCode.RequestedScopes.ToSpaceSeparatedString(), 12 }; 13 // 若是須要生成refreshtoken就設置值 14 if (refreshToken.IsPresent()) 15 { 16 response.RefreshToken = refreshToken; 17 } 18 // AuthorizationCode受權的生成idtoken,包括Hybridflow等 19 if (request.ValidatedRequest.AuthorizationCode.IsOpenId) 20 { 21 Client client = null; 22 if (request.ValidatedRequest.AuthorizationCode.ClientId != null) 23 { 24 client = await Clients.FindEnabledClientByIdAsync(request.ValidatedRequest.AuthorizationCode.ClientId); 25 } 26 if (client == null) 27 { 28 throw new InvalidOperationException("Client does not exist anymore."); 29 } 30 var resources = await Resources.FindEnabledResourcesByScopeAsync(request.ValidatedRequest.AuthorizationCode.RequestedScopes); 31 var tokenRequest = new TokenCreationRequest 32 { 33 Subject = request.ValidatedRequest.AuthorizationCode.Subject, 34 Resources = resources, 35 Nonce = request.ValidatedRequest.AuthorizationCode.Nonce, 36 AccessTokenToHash = response.AccessToken, 37 ValidatedRequest = request.ValidatedRequest 38 }; 39 var idToken = await TokenService.CreateIdentityTokenAsync(tokenRequest); 40 var jwt = await TokenService.CreateSecurityTokenAsync(idToken); 41 response.IdentityToken = jwt; 42 } 43 // 返回response 44 return response; 45 }
1 internal class TokenResult : IEndpointResult 2 { 3 public TokenResponse Response { get; set; } 4 public TokenResult(TokenResponse response) 5 { 6 if (response == null) throw new ArgumentNullException(nameof(response)); 7 Response = response; 8 } 9 // 把tokenresponse寫入context輸出流,json格式 10 public async Task ExecuteAsync(HttpContext context) 11 { 12 context.Response.SetNoCache(); 13 var dto = new ResultDto 14 { 15 id_token = Response.IdentityToken, 16 access_token = Response.AccessToken, 17 refresh_token = Response.RefreshToken, 18 expires_in = Response.AccessTokenLifetime, 19 token_type = OidcConstants.TokenResponse.BearerTokenType, 20 scope = Response.Scope 21 }; 22 if (Response.Custom.IsNullOrEmpty()) 23 { 24 await context.Response.WriteJsonAsync(dto); 25 } 26 else 27 { 28 var jobject = ObjectSerializer.ToJObject(dto); 29 jobject.AddDictionary(Response.Custom); 30 await context.Response.WriteJsonAsync(jobject); 31 } 32 } 33 // ids4的團隊也有點隨意啊。 34 internal class ResultDto 35 { 36 public string id_token { get; set; } 37 public string access_token { get; set; } 38 public int expires_in { get; set; } 39 public string token_type { get; set; } 40 public string refresh_token { get; set; } 41 public string scope { get; set; } 42 } 43 }