Давай дружить. OpenId Connect и Yarp

от автора

Предисловие

Сегодня в этой статье я хочу поделиться личным опытом работы и решением конкретного кейса. Работали над ним небольшой командой, но для простоты повествования буду писать от первого лица. Собственно сам кейс: есть Windows сервер с доменом и SSL сертификатом, на нём нужно поднять сервер авторизации с протоколом OpenId Connect и ещё два приложения, которые должны авторизовываться через сервер авторизации. И да, реализовать все нужно по максимуму использовав встроенный функционал. 

“Сервер авторизации должен быть вынесен отдельно!” — скажете вы – Но в реальности крутим и вертим как можем. Звучит просто, как два пальца. Как было на деле сейчас расскажу.

Общая схема

Сервер авторизации

Тут все относительно просто, есть готовые примеры и описанная документация. Бери да делай! В качестве основы был использован  пример у damienbod . И уже дальше доработан под себя как нужно.

YARP

YARP – обратный прокси сервер, относительно недавно выпущенный в релиз компанией Microsoft. В его настройке нет ничего сложного, есть описанная документация и примеры имплементации. Конкретно мы руководствовались вот этой статьей. Конфигурацию нашего прокси прикладываю ниже.

"ReverseProxy": {     "Routes": {       "minimumroute": {         "ClusterId": "minimumcluster",         "Match": {           "Path": "{**catch-all}"         }       },       "route2": {         "ClusterId": "Server",         "Match": {           "Path": "/Server/{*any}"         },         "Transforms": [           {             "PathRemovePrefix": "/Server"           }         ]       },       "route3": {         "ClusterId": "App1",         "Match": {           "Path": "/App1/{*any}"         },         "Transforms": [           {             "PathRemovePrefix": "/app1"           }         ]       },       "route4": {         "ClusterId": "App2",         "Match": {           "Path": "/App2/{*any}"         },         "Transforms": [           {             "PathRemovePrefix": "/app2"           }         ]       }         ]       }     },     "Clusters": {       "minimumcluster": {         "Destinations": {           "first_destination": {             "Address": "http://localhost:5001"           }         }       },       "Server": {         "Destinations": {           "first_destination": {             "Address": "http://localhost:5001"           }         }       },       "App1": {         "Destinations": {           "first_destination": {             "Address": "http://localhost:5002"           }         }       },       "App2": {         "Destinations": {           "first_destination": {             "Address": "http://localhost:5003"           }         }       }     }   } 

Вот и всё, готово?

Начинаем все запускать локально и неужели все работает – да? Ага, значит деплоим и вот незадача – не работает. В чем же ошибка? 

Дело в том, что у нашего сервера авторизации адрес http:// localhost:5001, а у приложения http:// localhost:5002. Поэтому когда, все это развернуто на сервере происходит ошибка. Для решения этой проблемы нам нужно самим указать такой параметр как RedirectUri

options.Events.OnRedirectToIdentityProvider = ctx => {   ctx.ProtocolMessage.RedirectUri = "https://domain/app1/signin-oidc";   return Task.CompletedTask; }; options.Events.OnRedirectToIdentityProviderForSignOut = ctx => {   ctx.ProtocolMessage.PostLogoutRedirectUri = "https://domain/app1/signout-callback-oidc";   return Task.CompletedTask; };

Уточнение: signin-oidc и signout-callback-oidc – это встроенные методы, который устанавливается по умолчанию как конечные точки входа и выхода в пакете Microsoft.AspNetCore.Authentication.OpenIdConnect.

С одной проблемой разобрались, теперь редирект правильный. Но встречаем тут же следующую, которая звучит как Corellation failed. Заходим в панель разработчика и начинаем пристально изучать запросы и ответы. И находим предупреждение от браузера, что файлы cookie не установлены, так, как отсутствует атрибут secure. Путей решения этой проблемы два:

  1. Установить для наших приложений самозаверяющиеся SSL сертификаты. Как это сделать описано здесь.

  2. Либо на наших приложениях изменить конфигурацию политики файлов cookie. Для этого нам нужно прописать следующее в program.cs:

builder.Services.Configure<CookiePolicyOptions>(options => {   options.Secure = CookieSecurePolicy.Always; });
app.UseCookiePolicy();

Всё, с этой проблемой справились. Заходим, пробуем, и снова тоже самое. Изучаем, значиться, дальше. И наконец находим, что файлы cookie то установились, но по неверному маршруту. Значит теперь самим вручную нужно указать маршрут установки. Делается это следующим образом, в настройках аутентификации OpenIdConnect

options.CorrelationCookie.Path = "/app1/signin-oidc"; options.NonceCookie.Path = "/app1/signin-oidc";

И наконец все работает! Полный код файла program.cs ниже.

var builder = WebApplication.CreateBuilder(args); IConfiguration configuration = builder.Configuration; builder.Services.AddHttpClient(); builder.Services.AddOptions(); builder.Services.AddAuthentication(options => {     options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;     options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }).AddCookie()         .AddOpenIdConnect(options =>         {             options.SignInScheme = "Cookies";             options.Authority = "https://domain.com/server";             options.ClientId = "ClientId";             options.ClientSecret = "ClientSecret";             options.ResponseType = OpenIdConnectResponseType.Code;             options.UsePkce= true;             options.Scope.Add("openid");             options.Scope.Add("profile");             options.SaveTokens = true;             options.GetClaimsFromUserInfoEndpoint = true;             options.CorrelationCookie.Path = "/app1/signin-oidc";             options.NonceCookie.Path = "/app1/signin-oidc";             options.Events.OnRedirectToIdentityProvider = ctx =>             {                 ctx.ProtocolMessage.RedirectUri = "https://domain.com/server/signin-oidc";                 return Task.CompletedTask;             };             options.Events.OnRedirectToIdentityProviderForSignOut = ctx =>             {                 ctx.ProtocolMessage.PostLogoutRedirectUri = "https://domain.com/server/signout-callback-oidc";                 return Task.CompletedTask;             };         });  builder.Services.Configure<CookiePolicyOptions>(options => {     options.Secure = CookieSecurePolicy.Always; });   var app = builder.Build(); IWebHostEnvironment env = app.Environment; if (app.Environment.IsDevelopment()) {     app.UseDeveloperExceptionPage();     app.UseWebAssemblyDebugging();      } else {     app.UseHsts(); } app.UsePathBase("/App1"); app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseCookiePolicy(); app.UseAuthentication(); app.UseAuthorization(); app.MapRazorPages(); app.MapControllers(); app.MapFallbackToFile("Index.html"); app.Run();

Небольшое послесловие

Возможно описанный мной способ решения данного кейса не на 100% верный, но разрабатывался он исходя из тех исходных данных, которые были нам предоставлены. В просторах интернета не нашел похожего решения данного кейса, или хотя бы похожего, поэтому решил поделиться. Если у вас есть, вариант как можно усовершенствовать этот способ или сделать по иному но, в тех же рамках, то прошу в комментарии.


ссылка на оригинал статьи https://habr.com/ru/post/665482/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *