Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Дмитрий Фёдоров «SSO на базе OpenId Connect в корпоративной системе»

Дмитрий Фёдоров «SSO на базе OpenId Connect в корпоративной системе»

В докладе мы немного выйдем за рамки quick start’ов, рассмотрим, как прикрутить OIDC к унаследованному коду MVC и не только. Как организовать Single Sign Out в наборе внутрикорпоративных приложений, расширить «коробочный» функционал Identity Server’а, организовать масштабирование и другие вопросы из процесса реальной разработки реальных приложений.

Ceecdee9ee77b63d81100be62b7e1090?s=128

DotNetRu

March 14, 2019
Tweet

Transcript

  1. SSO на базе OpenId Connect В корпоративной системе. Организация и

    fine-tuning. Дмитрий Федоров
  2. 2

  3. 3

  4. А что же будет? •OIDC + MVC + SPA •Single

    Sign Out •Разное 4
  5. Кому всё это нужно? Ленивым пользователям • не нужно логиниться

    на каждом сайте отдельно • меньше паролей – лучше один, но надёжный 5 Хитрым разработчикам • проще инфраструктура • меньше персональных данных
  6. 6 Competences Salary review Time reporting Inbox System administration Help

    desk Payroll Trip & Expanses HR
  7. SSO – Это… Когда удобно пользователю! Идеальный случай: Windows identity

    • протоколы NTLM/Kerberos • логин и пароль только при входе в систему • но есть ложка дёгтя 7
  8. SSO – Это… WIF (ex- project Geneva, представлен в ‘09

    совместно с ADFS) • протокол WS-Federation, токен в формате SAML 1.0 (SOAP) • работает в вебе, но можно подружить с Windows identity • но есть ложка дёгтя 8
  9. Альтернативные протоколы •2005 Brad Fitzpatrick: Yadis Yet Another Distributed Identity

    System • 2006…2012 Blaine Cook, Chris Messina, Twitter: Oauth 9
  10. 10

  11. Implicit Flow 11 Secret resource IDP Identity server User-Agent (Client)

  12. Client app Authorization Code Flow 12 Secret resource IDP Identity

    Server PKCE расширение для мобильников code_verifier code_challenge
  13. http://identityserver.io/ 13 Brock Allen aka BrockAllen Dominick Baier aka leastprivilege

  14. Пример из практики. Есть рабочее приложение… 14 https://mysrv.mysite.com Other apps

    /tenantX/ /WebAppA/ /WebAppB/ /WebAppC/ /Login/ IIS /tenantY/
  15. Tenant cookie? Tenant A? https://supersite.com /Reporting/ https://login.supersite.com /Balances/ Connect request

    to SRV001:8502 Connect request to SRV001:8504 Other apps Tenant B? Mobile apps WEB API А хотим: общий IdP + тенанты на докер-хостах. 15 Identity Server
  16. Отдельно взятый тенант крупным планом. 16 Url contains /Balances/ Url

    contains /Reporting/ https://login.supersite.com Connect request to VIRTUALSERVER001:8502 Connect request to VIRTUALSERVER001:8504 Process the request Process the request Request Other apps
  17. <system.webServer> <modules runAllManagedModulesForAllRequests="true"> <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule, System.IdentityModel.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

    preCondition="managedHandler" /> <add name="WSFederationAuthenticationModule" preCondition="integratedMode" type="AdvancedWSFederationAuthenticationModule" /> </modules> </system.webServer> <system.identityModel.services> <federationConfiguration> <cookieHandler requireSsl="false" path="/Dev/ClientApp/"> <chunkedCookieHandler chunkSize="4000" /> </cookieHandler> <wsFederation passiveRedirectEnabled="true" issuer="http://domain.com/IDP/" realm="http://domain.com/ClientApp/" requireHttps="false" /> <serviceCertificate> <!-- Thumbprint for the encryption certificate --> <certificateReference x509FindType="FindByThumbprint" findValue="FC6BB85A57488178C90161DE55A7948F00C756E6" /> </serviceCertificate> </federationConfiguration> </system.identityModel.services> <system.identityModel> <identityConfiguration> <audienceUris> <add value="http://dev.domain.com/ClientApp/" /> </audienceUris> <issuerNameRegistry> <trustedIssuers> <!-- Thumbprint for the singing certificate --> <add thumbprint="FC6BB85A57488178C90161DE55A7948F00C756E6" name="ssl-test" /> </trustedIssuers> </issuerNameRegistry> </identityConfiguration> </system.identityModel> Переключаемся с WIFа на OIDC 17 <system.webServer> <modules runAllManagedModulesForAllRequests="true"> <add name="SessionAuthenticationModule" type="System.IdentityModel.Services.SessionAuthenticationModule" preCondition="managedHandler" /> <add name="WSFederationAuthenticationModule" preCondition="integratedMode" type="AdvancedWSFederationAuthenticationModule" /> </modules> </system.webServer> <system.identityModel.services> <federationConfiguration> <cookieHandler requireSsl="false“ path="/Dev/ClientApp/"> <chunkedCookieHandler chunkSize="4000" /> </cookieHandler> <wsFederation passiveRedirectEnabled="true“ issuer=http://dev.domain.com/IDP/ realm=http://dev.domain.com/ClientApp/ requireHttps="false" /> <serviceCertificate> <!-- Thumbprint for the encryption certificate --> <certificateReference x509FindType="FindByThumbprint" findValue="FC6BB85A57488178C90161DE55A7948F00C756E6" /> </serviceCertificate> </federationConfiguration> </system.identityModel.services> <system.identityModel> <identityConfiguration> <audienceUris> <add value="http://dev.domain.com/ClientApp/" /> </audienceUris> <issuerNameRegistry> <trustedIssuers> <!-- Thumbprint for the singing certificate --> <add thumbprint="FC6BB85A57488178C90161DE55A7948F00C756E6" name="ssl-test" /> </trustedIssuers> </issuerNameRegistry> </identityConfiguration> </system.identityModel>
  18. В ASP.NET classic используем OIDC-клиента для OWIN • Вместо модулей

    и хендлеров, выполняются middleware-s • Декларативная безопасность из конфига переехала в код. • Добавим в приложение класс Startup, содержащий метод: public void Configuration(Owin.IAppBuilder app){} • Продолжаем использовать AuthorizeAttribute из System.Web.Mvc. • Используем [AllowAnonymous] там, где это необходимо. 18 public void Configuration(Owin.IAppBuilder app){ app.SetDefaultSignInAsAuthenticationType( CookieAuthenticationDefaults.AuthenticationType); app.UseCookieAuthentication( new CookieAuthenticationOptions{} ); app.UseOpenIdConnectAuthentication( new OpenIdConnectAuthenticationOptions() { }); } Пропишем в Application_Start: RegisterGlobalFilters.(GlobalFiltersCollection filters){ filters.Add(new HandleErrorAttribute()); filters.Add(new AuthorizeAttribute()); }
  19. А в конфиге у нас теперь: <applicationSettings> <MyCompany.Owin.OidcHelper.OpenIdConnectAuthentication> <setting name="Authority"

    serializeAs="String"> <value>http://localhost:5100</value> </setting> <setting name="RedirectUri" serializeAs="String"> <value>http://localhost/IdSrvTestApp/</value> </setting> <setting name="ClientId" serializeAs="String"> <value>localhost-testapp</value> </setting> </MyCompany.Owin.OidcHelper.OpenIdConnectAuthentication> </applicationSettings> 19
  20. Попробуем на практике 20

  21. А что же OWIN? А если у нас даже не

    MVC, а ASP.NET WEB Forms? 21 app.UseStageMarker(PipelineStage.Authenticate); SessionAuthenticationModule WSFederationAuthenticationModule public enum PipelineStage{ Authenticate, PostAuthenticate, Authorize, PostAuthorize, ResolveCache, PostResolveCache, MapHandler, PostMapHandler, AcquireState, PostAcquireState, PreHandlerExecute, }
  22. Troubleshooting: как избежать зацикливания • CookieManager = new SystemWebChunkingCookieManager() в

    OpenIdConnectAuthenticationOptions и CookieAuthenticationOptions • Следим, что return URI совпадает с исходным, в части протокола и хоста, например: 22 app.Use(async (context, next) => { if (<какой-то сеттинг>) context.Request.Scheme = Uri.UriSchemeHttps; await next.Invoke(); });
  23. Пара слов про SPA, в частности Angular https://github.com/damienbod/angular-auth-oidc-client •Разработан специально

    для Angular 2+ •Implicit, Code + PKCE flow •Api call interceptor included 23
  24. С вебом всё замечательно Теперь не забыть защитить API •

    В простейшем случае: .AddJwtBearer(options => new JwtBearerOptions() { Audience = "http://localhost:5001/", Authority = "http://localhost:5000 }); • Authorization policy = AuthorizationRequirement + AuthorizationHandler 24 Например, services.AddAuthorization(options => { options.AddPolicy("OfficeNumberUnder200", policy => policy.Requirements.Add( new MaximumOfficeNumberRequirement(200))); }); services.AddSingleton<IAuthorizationHandler, MaximumOfficeNumberAuthorizationHandler>(); И далее: [Authorize(Policy = "OfficeNumberUnder200")]
  25. 25 Cloud ready: •OIDC через OWIN в MVC 5 •Или

    нативно в ASP.NET Core •Или SPA на Angular 6 •Bearer token в WEB API
  26. Identity Server Configuration public void ConfigureServices(IServiceCollection services) { var cert

    = new X509Certificate2(“cert.pfx", "the-password"); services.AddIdentityServer() .AddInMemoryApiResources(GetApiResources()) .AddSigningCredential(cert) .AddInMemoryIdentityResources(GetIdentityResources()) .AddProfileService<UserProfileService>() } 26 Config.GetSection(nameof(IdentityServerClients)).Bind(myClients); services.AddSingleton(myClients); .AddInMemoryClients(myClients.Clients)
  27. Если клиентов не слишком много, сконфигурируем их в appsettings.json 27

  28. "IdentityServerClients": { "WebAppClients": [ {"ClientId": "super-app1", "RedirectUri": "https://app.example/super-app1/", "AllowedScopes": ["graph-api","graph-api.backend"]

    }], "NativeAppClients": [ {"ClientId": "simple-native-app", "RedirectUri": "com.company.app:/oauth2callback", "AllowedScopes": [ "graph-api" ] } ], "ServiceClients": [ { "ClientId": "xxx-cli-client", "ClientSecret": "xxx-secret-2018", "AllowedScopes": [ "config-api", "config-api.admin" ]}] } 28
  29. Кастомизируем Claims через Scope Для аутентификации… public static List<IdentityResource> GetIdentityResources()

    { var openIdScope = new IdentityResources.OpenId(); openIdScope.UserClaims.Add(JwtClaimTypes.Locale); return new List<IdentityResource> { openIdScope, new IdentityResources.Profile(), new IdentityResources.Email(), new IdentityResource(Constants.RolesScopeType, Constants.RolesScopeType, new List<string> {JwtClaimTypes.Role, Constants.TenantIdClaimType}) }; } 29
  30. Кастомизируем Claims через Scope И для авторизации public static IEnumerable<ApiResource>

    GetApiResources(){ return new List<ApiResource>{ new ApiResource{ Name = "some-test-api", Scopes = { new Scope{ Name = "some-api", UserClaims = { JwtClaimTypes.SessionId, JwtClaimTypes.Role, Constants.TenantIdClaimType, JwtClaimTypes.Email, JwtClaimTypes.Locale }}}}}; } 30
  31. Grant types •Implicit •Authorization code •Hybrid •Client credentials •Resource owner

    password •Refresh tokens •Extension grants 31
  32. Grant types при регистрации клиента: Client.AllowedGrantTypes = GrantTypes.HybridAndClientCredentials; Client.AllowedGrantTypes =

    { GrantType.Hybrid, GrantType.ClientCredentials, "my_custom_grant_type“ }; 32
  33. API 1 Extension Grant для делегирования прав 33 API 2

    IDP Identity Server Client app delegation token { client_id:”front_end”, sub:”123”, scope:[“api1”] } { client_id:”api1”, sub:”123”, scope:[“api2”] }
  34. Extension Grant для делегирования прав 34 public class DelegationGrantValidator:IExtensionGrantValidator {

    public string GrantType => "delegation"; public async Task ValidateAsync(ExtensionGrantValidationContext context) { var userToken = await ValidateAccessTokenAsync( context.Request.Raw.Get("token")); // get user's identity var sub = userToken.Claims.FirstOrDefault(c => c.Type == JwtClaimTypes.Subject)?.Value; context.Result = new GrantValidationResult( sub, GrantType, userToken.Claims, Constants.MyIdIdpName); } }
  35. Чего нам не хватает – мультиинстантности задействууем REDIS • Сертификат

    для подписи токенов services.AddSigningCredential(cert); • Замена MachineKey, c версии 2.0 появилась поддержка Redis services.AddDataProtection() .SetApplicationName(typeof(Startup).Namespace) .PersistKeysToRedis(redis, "DataProtection-Keys"); • PersistedGrantStore для хранения токенов, Consens services.AddSingleton<IPersistedGrantStore, RedisPersistedGrantStore>().Configure( (Action<PersistedGrantStoreOptions>) (options => options.DatabaseFactory = () => redis.GetDatabase(-1, null))); 35
  36. Persistent token store, всего 5 методов public interface IPersistedGrantStore {

    Task StoreAsync(PersistedGrant grant); Task<PersistedGrant> GetAsync(string key); Task<IEnumerable<PersistedGrant>> GetAllAsync(string subjectId); Task RemoveAsync(string key); Task RemoveAllAsync(string subjectId, string clientId); } 36
  37. Масштабирование: •Общий сертификат •Хранилище ключей •Хранилище грантов 37

  38. Single sign out Просто sign out и Single sign out

    В чём разница? Из всех клиентов сразу! 38
  39. Single sign out •Front-Channel Logout 1.0 - draft 02 IFrame

    для каждого клиента (RP) •Back-Channel Logout 1.0 - draft 04 OP посылает JWT (Logout Token) 39
  40. Попробуем на практике 40

  41. Single sign out + External IdP? Легко! •Стандартный SignOutAsync(ExternalAuthenticationScheme) •Вызов

    внешнего LogoutEndpoint’a со странички LoggedOut 41
  42. 42

  43. Локализация Задействуем RequestLocalizationMiddleware app.UseRequestLocalization( new RequestLocalizationOptions{ DefaultRequestCulture = new RequestCulture("en-GB"),

    SupportedCultures = Constants.SupportedCultures, SupportedUICultures = Constants.SupportedCultures }); Добавим культуру в токены if (HttpContext?.Features.Get<IRequestCultureFeature>()?.Provider != null) { var requestedCulture = rcf.RequestCulture.Culture.TextInfo.CultureName; additionalClaims.Add(new Claim(JwtClaimTypes.Locale, requestedCulture)); } else // залогируем, что не нашли культуры в реквесте 43
  44. Доступ к микросервисам из самомго IS-хоста. private readonly IdentityServerTools idSrvTools;

    var apiAccessToken = await idSrvTools.IssueClientJwtAsync( "internal-identity-client", 12 * 60 * 60, new[] {Constants.ApiName, $"{Constants.ApiName}.admin" }, //scopes new[] {Constants.ConfigApiName} //audiences ); apiClient.SetBearerToken(apiAccessToken); 44
  45. Нужно вывесить API на IS-хосте? Валидация токена без сетевого обмена

    github.com/Kahbazi/IdentityServer4.Contrib.LocalAccessTokenValidation services.AddAuthentication() .AddLocalAccessTokenValidation( "token“ ,isAuth => { }); 45
  46. •SSO •OIDC •Группа бизнес приложений •Identity Server 4 •Static Client

    config •Grant Types •Custom Grants •Single Sign Out 46
  47. Вопросы и ресурсы • https://github.com/dfrunet/ – примеры из этого доклада

    • https://openid.net/connect/ • https://github.com/IdentityServer • https://leastprivilege.com/ • https://www.scottbrady91.com/Identity-Server/ • https://docs.microsoft.com/en-us/aspnet/aspnet/overview/owin-and- katana/owin-middleware-in-the-iis-integrated-pipeline • https://blogs.msdn.microsoft.com/webdev/2017/04/06/jwt-validation-and- authorization-in-asp-net-core/ 47
  48. Contact Dmitry Fedorov dmitriy.fedorov@arcadia.spb.ru 48