简体   繁体   English

单租户应用程序需要通过 OpenId Owin 动态支持来自多租户提供商的 SSO

[英]Single Tenant Application needs to support SSO from a multi-tenant provider dynamically with OpenId Owin

I'm trying to use OpenId to authenticate against a dynamic authority URL.我正在尝试使用 OpenId 对动态授权 URL 进行身份验证。 There is an unknown number of different {n-tenant}.identityProvider.com authority URLs.存在未知数量的不同{n-tenant}.identityProvider.com授权 URL。 So I need to be able to pass through the n-tenant that the user is accessing my application through and configure UseOpenIdConnectAuthentication dynamically as users attempt to sign in.因此,我需要能够通过用户访问我的应用程序的n-tenant ,并在用户尝试登录时动态配置UseOpenIdConnectAuthentication

This means that I won't know what the Authority URL is at startup because I wouldn't know which n-tenant to register.这意味着我在启动时不知道授权 URL 是什么,因为我不知道要注册哪个n-tenant I would only know what the Authrity URL is after a tenant attempts to access My Application's Sign In endpoint because it will have an n-tenant value in the URL.我只会在租户尝试访问我的应用程序的登录端点后才知道Authrity URL 是什么,因为它在 URL 中有一个n-tenant值。

I attempted to use the RedirectToIdentityProvider notification to reconfigure n.Options.Authority , but that didn't work.我尝试使用RedirectToIdentityProvider通知重新配置n.Options.Authority ,但这不起作用。 Also, omitting the Authority configuration at startup causes an exception.此外,在启动时省略Authority配置会导致异常。

The code below works correctly if I hardcode a specific n-tenant .如果我对特定的n-tenant进行硬编码,则下面的代码可以正常工作。 However, I can't figure out how to dynamically configure the OpenIdConnectAuthenticationOptions to use a dynamic n-tenant value within it's authority URL.但是,我无法弄清楚如何动态配置OpenIdConnectAuthenticationOptions以在其权限 URL 中使用动态n-tenant值。

Please note that the ClientId and ClientSecret will be the SAME for all n-tenant .请注意,所有n-tenant的 ClientId 和 ClientSecret 都是相同的。 Only the endpoints need to be dynamic.只有端点需要是动态的。

I'm using ASP.NET Forms with .NET Framework 4.8我正在使用带有 .NET Framework 4.8 的 ASP.NET 表单

在此处输入图像描述

Startup.cs启动.cs

using IdentityModel.Client;
using Microsoft.AspNet.Identity;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin;
using Microsoft.Owin.Host.SystemWeb;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http;
using System.Security.Claims;

[assembly: OwinStartup(typeof(MyApplication.Startup))]
namespace MyApplication
{
    public class Startup
    {
        private readonly string _clientId = "CLIENT_ID";
        private readonly string _clientSecret = "CLIENT_SECRET";

        private readonly string _redirectUri = "https://myapplication.com/{n-tenant}/oidc-callback";
        private readonly string _authority = "https://identityprovider.com/{n-tenant}/";
        

        public void Configuration(IAppBuilder app)
        {
            JwtSecurityTokenHandler.DefaultMapInboundClaims = false;

            ConfigureAuth(app);
        }

        public void ConfigureAuth(IAppBuilder app)
        {
            app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

            app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

            app.UseCookieAuthentication(new CookieAuthenticationOptions());


            app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions()
            {
                ClientId = _clientId,
                ClientSecret = _clientSecret,
                Authority = _authority,
                RedirectUri = _redirectUri,
                ResponseType = OpenIdConnectResponseType.Code,
                Scope = OpenIdConnectScope.OpenId,
                TokenValidationParameters = new TokenValidationParameters { NameClaimType = "sub" },
                CallbackPath = new PathString("/{n-tenant}/oidc-callback"),
                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    RedirectToIdentityProvider = async n =>
                    {
                    },
                    AuthorizationCodeReceived = async n =>
                    {
                        using (var client = new HttpClient())
                        {

                            var tokenResponse = await client.RequestAuthorizationCodeTokenAsync(new AuthorizationCodeTokenRequest
                            {
                                Address = $"{_authority}/connect/token",
                                ClientId = _clientId,
                                ClientSecret = _clientSecret,
                                Code = n.Code,
                                RedirectUri = _redirectUri
                            });

                            if (tokenResponse.IsError)
                            {
                                throw new Exception(tokenResponse.Error);
                            }

                            n.TokenEndpointResponse = new OpenIdConnectMessage(tokenResponse.Raw);
                        }

                    }
                },
                
            });
        }
}

It will be hard to post an answer which will exactly help you solve the issue because only way to check if it works is to reproduce you entire environment which might be impossible.很难发布一个完全可以帮助您解决问题的答案,因为检查它是否有效的唯一方法是重现您可能不可能的整个环境。

It is very hard to find any kind of documentation for those libraries but usually when I deal with any OAuth or OpenIDConnect flows I use those repositories: Katana or IdentityModel很难为这些库找到任何类型的文档,但通常当我处理任何 OAuth 或 OpenIDConnect 流程时,我会使用这些存储库: KatanaIdentityModel

I think your idea about using RedirectToIdentityProvider is correct.我认为您关于使用RedirectToIdentityProvider的想法是正确的。 That is the place in Katana code which is being called before redirecting user to your identity server.这是在将用户重定向到您的身份服务器之前调用的 Katana 代码中的位置。 So you can modify parameters which are used when Url is getting build.因此,您可以修改在构建 Url 时使用的参数。 You could start your lambda like that:你可以像这样开始你的 lambda:

RedirectToIdentityProvider = async n =>
{
   if (n.ProtocolMessage.RequestType == Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectRequestType.Authentication)
   {
      n.ProtocolMessage.Parameters["redirect_uri"] = //your whole url here

I think it is also good idea to debug this place or log somewhere what parameters you have in that context.我认为调试这个地方或在某处记录你在那个上下文中的参数也是一个好主意。 Because I believe you should have a parameter or property similar to IssuerAddress .因为我相信你应该有一个类似于IssuerAddress的参数或属性。 This should be address to your identity server - you can modify it as well.这应该是您的身份服务器的地址 - 您也可以修改它。

If you will set those values correctly whole flow will unfortunately still not work.如果您正确设置这些值,不幸的是整个流程仍然无法正常工作。 It is because CallbackPath is used in middleware handler to identify if request which comes to your application is part of OAuth flow.这是因为在中间件处理程序中使用CallbackPath来识别到达您的应用程序的请求是否是 OAuth 流的一部分。 You can find this code here .您可以在此处找到此代码。 Without that your AuthorizationCodeReceived handler will not get called.否则,您的AuthorizationCodeReceived处理程序将不会被调用。

So now the question is - do you really need separate CallbackPath .所以现在的问题是 - 你真的需要单独的CallbackPath Based on your question you are saying that application will have callbacks set like "https://myapplication.com/{n-tenant}/oidc-callback" but really what is happening there is that middleware takes the code (after user logs in) and exchange it in backchannel (server to server request) for access_token.根据您的问题,您说应用程序将设置像"https://myapplication.com/{n-tenant}/oidc-callback"这样的回调,但实际上发生的是中间件获取代码(在用户登录后) 并在反向通道(服务器到服务器请求)中将其交换为 access_token。 In default flow that token gets stored in browser cookie - and in your scenario it looks like all your applications share the same cookie (are hosted on the same domain).在默认流程中,令牌存储在浏览器 cookie 中 - 在您的场景中,您的所有应用程序看起来都共享相同的 cookie(托管在同一个域上)。 So it is really hard to tell what is your expected behaviour after user logs in.所以很难说用户登录后你的预期行为是什么。

If that separation is "must have" in your scenario then only path I can see is really to use that Katana repository code and craft your own implemenation of OpenidConnectAuthenticationHandler .如果在您的场景中这种分离是“必须的”,那么我能看到的唯一路径就是使用 Katana 存储库代码并制作您自己的OpenidConnectAuthenticationHandler实现。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM