關於IdentityServer4與ocelot博客園裡已經有很多介紹我這裡就不再重覆了。 ocelot與IdentityServer4組合認證博客園裡也有很多,但大多使用ocelot內置的認證,而且大多都是用來認證API的,查找了很多資料也沒看到如何認證oidc,所以這裡的ocelot實際只是作為 ...
關於IdentityServer4與ocelot博客園裡已經有很多介紹我這裡就不再重覆了。
ocelot與IdentityServer4組合認證博客園裡也有很多,但大多使用ocelot內置的認證,而且大多都是用來認證API的,查找了很多資料也沒看到如何認證oidc,所以這裡的ocelot實際只是作為統一入口而不參與認證,認證的完成依然在客戶端。代碼是使用IdentityServer4的Quickstart5_HybridAndApi 示例修改的。項目結構如下
一 ocelot網關
我們先在示例添加一個網關。
修改launchSettings.json中的埠為54660
"NanoFabricApplication": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "http://localhost:54660", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }
配置文件如下
{ "ReRoutes": [ { // MvcClient "DownstreamPathTemplate": "/MvcClient/{route}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 50891 } ], "UpstreamPathTemplate": "/MvcClient/{route}", "UpstreamHeaderTransform": { "X-Forwarded-For": "{RemoteIpAddress}" } }, { // signin-oidc "DownstreamPathTemplate": "/signin-oidc", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 50891 } ], "UpstreamPathTemplate": "/signin-oidc", "UpstreamHeaderTransform": { "X-Forwarded-For": "{RemoteIpAddress}" } }, { // signout-callback-oidc "DownstreamPathTemplate": "/signout-callback-oidc", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 50891 } ], "UpstreamPathTemplate": "/signout-callback-oidc", "UpstreamHeaderTransform": { "X-Forwarded-For": "{RemoteIpAddress}" } }, { // MyApi "DownstreamPathTemplate": "/MyApi/{route}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 50890 } ], "UpstreamPathTemplate": "/MyApi/{route}", "UpstreamHeaderTransform": { "X-Forwarded-For": "{RemoteIpAddress}" } }, { // IdentityServer "DownstreamPathTemplate": "/{route}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 50875 } ], "UpstreamPathTemplate": "/IdentityServer/{route}", "UpstreamHeaderTransform": { "X-Forwarded-For": "{RemoteIpAddress}" } }, { // IdentityServer "DownstreamPathTemplate": "/{route}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 50875 } ], "UpstreamPathTemplate": "/{route}", "UpstreamHeaderTransform": { "X-Forwarded-For": "{RemoteIpAddress}" } } ] }View Code
這裡我們定義3個下游服務,MvcClient,MyApi,IdentityServer,並使用路由特性把signin-oidc,signout-callback-oidc導航到MvcClient,由MvcClient負責生成最後的Cooike。並將預設路由指定到IdentityServer服務。
在ConfigureServices中添加Ocelot服務。
services.AddOcelot() .AddCacheManager(x => { x.WithDictionaryHandle(); }) .AddPolly()
在Configure中使用Ocelot中間件
app.UseOcelot().Wait();
Ocelot網關就部署完成了。
二 修改QuickstartIdentityServer配置
首先依然是修改launchSettings.json中的埠為50875
在ConfigureServices中修改AddIdentityServer配置中的PublicOrigin和IssuerUri的Url為http://localhost:54660/IdentityServer/
services.AddIdentityServer(Option => { Option.PublicOrigin = "http://localhost:54660/IdentityServer/"; Option.IssuerUri = "http://localhost:54660/IdentityServer/"; }) .AddDeveloperSigningCredential() .AddInMemoryIdentityResources(Config.GetIdentityResources()) .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()) .AddTestUsers(Config.GetUsers());
這樣一來發現文檔中的IdentityServer地址就變為網關的地址了,進一步實現IdentityServer的負載均衡也是沒有問題的。
修改Config.cs中mvc客戶端配置如下
ClientId = "mvc", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, // AccessTokenType = AccessTokenType.Reference, RequireConsent = true, RedirectUris = { "http://localhost:54660/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:54660/signout-callback-oidc" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api1" }, AllowOfflineAccess = true, //直接返回客戶端需要的Claims AlwaysIncludeUserClaimsInIdToken = true,
主要修改RedirectUris和PostLogoutRedirectUris為網關地址,在網關也設置了signin-oidc和signout-callback-oidc轉發請求到Mvc客戶端。
三 修改MvcClient
修改MvcClient的launchSettings.json埠為50891。
修改MvcClient的Authority地址為http://localhost:54660/IdentityServer和預設路由地址MvcClient/{controller=Home}/{action=index}/{id?}
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; //options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie("Cookies",options=> { options.ExpireTimeSpan = TimeSpan.FromMinutes(30); options.SlidingExpiration = true; }) .AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = "http://localhost:54660/IdentityServer"; options.RequireHttpsMetadata = false; options.ClientId = "mvc"; options.ClientSecret = "secret"; options.ResponseType = "code id_token"; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("api1"); options.Scope.Add("offline_access"); });View Code
app.UseMvc(routes => { routes.MapRoute( name: "default", template: "MvcClient/{controller=Home}/{action=index}/{id?}"); });View Code
修改HomeController,將相關地址修改為網關地址
public async Task<IActionResult> CallApiUsingClientCredentials() { var tokenClient = new TokenClient("http://localhost:54660/IdentityServer/connect/token", "mvc", "secret"); var tokenResponse = await tokenClient.RequestClientCredentialsAsync("api1"); var client = new HttpClient(); client.SetBearerToken(tokenResponse.AccessToken); var content = await client.GetStringAsync("http://localhost:54660/MyApi/identity"); ViewBag.Json = JArray.Parse(content).ToString(); return View("json"); } public async Task<IActionResult> CallApiUsingUserAccessToken() { var accessToken = await HttpContext.GetTokenAsync("access_token"); //OpenIdConnectParameterNames var client = new HttpClient(); client.SetBearerToken(accessToken); var content = await client.GetStringAsync("http://localhost:54660/MyApi/identity"); ViewBag.Json = JArray.Parse(content).ToString(); return View("json"); }View Code
四 修改Api項目
Api項目修改多一點。
將MvcClient的HomeController和相關視圖複製過來,模擬MVC與API同時存在的項目。
修改Api的launchSettings.json埠為50890。
修改Startup
public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddDataProtection(options => options.ApplicationDiscriminator = "00000").SetApplicationName("00000"); services.AddMvc(); JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }).AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = "http://localhost:54660/IdentityServer"; options.RequireHttpsMetadata = false; options.ClientId = "mvc"; options.ClientSecret = "secret"; options.ResponseType = "code id_token"; options.SaveTokens = true; options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("api1"); options.Scope.Add("offline_access"); }) .AddIdentityServerAuthentication("Bearer", options => { options.Authority = "http://localhost:54660/IdentityServer"; options.RequireHttpsMetadata = false; options.ApiSecret = "secret123"; options.ApiName = "api1"; options.SupportedTokens= SupportedTokens.Both; }); services.AddAuthorization(option => { //預設 只寫 [Authorize],表示使用oidc進行認證 option.DefaultPolicy = new AuthorizationPolicyBuilder("oidc").RequireAuthenticatedUser().Build(); //ApiController使用這個 [Authorize(Policy = "ApiPolicy")],使用jwt認證方案 option.AddPolicy("ApiPolicy", policy => { policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme); policy.RequireAuthenticatedUser(); }); }); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //var options = new ForwardedHeadersOptions //{ // ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost, // ForwardLimit = 1 //}; //options.KnownNetworks.Clear(); //options.KnownProxies.Clear(); //app.UseForwardedHeaders(options); //if (env.IsDevelopment()) //{ // app.UseDeveloperExceptionPage(); //} //else //{ // app.UseExceptionHandler("/Home/Error"); //} app.UseAuthentication(); app.UseStaticFiles(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "MyApi/{controller=MAccount}/{action=Login}/{id?}"); }); } }View Code
主要添加了oidc認證配置和配置驗證策略來同時支持oidc認證和Bearer認證。
修改IdentityController中的[Authorize]特性為[Authorize(Policy = "ApiPolicy")]
依次使用調試-開始執行(不調試)並選擇項目名稱啟動QuickstartIdentityServer,Gateway,MvcClient,Api,啟動方式如圖
應該可以看到Gateway啟動後直接顯示了IdentityServer的預設首頁
在瀏覽器輸入http://localhost:54660/MVCClient/Home/index進入MVCClient
點擊Secure進入需要授權的頁面,這時候會跳轉到登陸頁面(才怪
實際上我們會遇到一個錯誤,這是因為ocelot做網關時下游服務獲取到的Host實際為localhost:50891,而在IdentityServer中設置的RedirectUris為網關的54660,我們可以通過ocelot轉發X-Forwarded-Host頭,併在客戶端通過UseForwardedHeaders中間件來獲取頭。但是UseForwardedHeaders中間件為了防止IP欺騙攻擊需要設置KnownNetworks和KnownProxies以實現嚴格匹配。當然也可以通過清空KnownNetworks和KnownProxies的預設值來不執行嚴格匹配,這樣一來就有可能受到攻擊。所以這裡我直接使用硬編碼的方式設置Host,實際使用時應從配置文件獲取,同時修改MvcClient和Api相關代碼
app.Use(async (context, next) => { context.Request.Host = HostString.FromUriComponent(new Uri("http://localhost:54660/")); await next.Invoke(); }); //var options = new ForwardedHeadersOptions //{ // ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost, // ForwardLimit = 1 //}; //options.KnownNetworks.Clear(); //options.KnownProxies.Clear(); //app.UseForwardedHeaders(options);
在反向代理情況下通過轉發X-Forwarded-Host頭來獲取Host地址應該時常見設置不知道還有沒有其他更好的解決辦法。
再次啟動MVCClient並輸入http://localhost:54660/MvcClient/Home/Secure。
使用bob,password登陸一下
點擊Yes, Allow返回http://localhost:54660/MvcClient/Home/Secure,此時可以查看到登陸後的信息
分別點擊Call API using user token和Call API using application identity來驗證一下通過access_token和ClientCredent模式請求來請求API
成功獲取到返回值。
輸入http://localhost:54660/myapi/Home/index來查看API情況
請求成功。
點擊Secure從API項目查看用戶信息,此時展示信息應該和MvcClient一致
嗯,並沒有看到用戶信息而是又到了授權頁.....,這是因為.netCore使用DataProtection來保護數據(點擊查看詳細信息),Api項目不能解析由MvcClient生成的Cookie,而被重定向到了IdentityServer服務中。
在MvcClient和Api的ConfigureServices下添加如下代碼來同步密鑰環。
services.AddDataProtection(options => options.ApplicationDiscriminator = "00000").SetApplicationName("00000");
再次啟動MvcClient和Api項目併在瀏覽器中輸入http://localhost:54660/MvcClient/home/Secure,此時被要求重新授權,點擊Yes, Allow後看到用戶信息
再輸入http://localhost:54660/myapi/Home/Secure從API項目查看用戶信息
分別點擊Call API using user token和Call API using application identity來驗證一下通過access_token和ClientCredent模式請求來請求API
請求成功。
如此我們便實現了通過ocelot實現統一入口,通過IdentityServer4來實現認證的需求
源代碼 https://github.com/saber-wang/Quickstart5_HybridAndApi
參考
https://www.cnblogs.com/stulzq/category/1060023.html
https://www.cnblogs.com/xiaoti/p/10118930.html
https://www.cnblogs.com/jackcao/tag/identityserver4/