问题
We are trying to setup a white label on our project that uses OWIN(includes FB, google, Live logins). Is there a way to setup their API credential dynamically, say if they changes the domain the settings will change.
I think owin loads earlier than MVC? Is there a way that we can load it on Global.asax(Request)?
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
}
}
UPDATE:
In other words a single Application will host many domains and sub domains(white labeling).
回答1:
I have been googling on the same things today. Then I found a nice OWIN sample at http://aspnet.codeplex.com/SourceControl/latest#Samples/Katana/BranchingPipelines/BranchingPipelines.sln that explained the branching capabilities of OWIN. If I understand this sample correctly, you should be able to configure different OWIN stacks depending on request parameters such as host header, cookie, path or whatever using the app.Map() or app.MapWhen() methods.
Let's say you have 2 different DNS domains representing 2 customers with different login configs, you can initialize OWIN to use different configs depending on the value of the host header:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapWhen(ctx => ctx.Request.Headers.Get("Host").Equals("customer1.cloudservice.net"), app2 =>
{
app2.UseWsFederationAuthentication(...);
});
app.MapWhen(ctx => ctx.Request.Headers.Get("Host").Equals("customer2.cloudservice.net"), app2 =>
{
app2.UseGoogleAuthentication(...);
});
}
}
回答2:
I just went through an exercise in trying to do exactly this. Unfortunately, There is no way to directly inject middleware into a Katana-based host after startup. The reason for this is because in order for middleware to actually be used, it has to be composed together into the application delegate. Katana's implementation does this by calling IAppBuilder.Build(typeof(AppFunc))
, where AppFunc
is a using alias of the specified type for the application delegate: Func<IDictionary<string,object>, Task>
when initialization is complete. Here's the key line at the bottom of .Initialize:
AppFunc = (AppFunc)builder.Build(typeof(AppFunc));
The only opportunity you have to configure middleware is before this, during the configuration step in the startup class you write or by web.config
.
Just to be very clear about what won't work, I was experimenting with something like this:
public class Startup
{
public void Configuration(IAppBuilder app)
{
HomeController.Initialized += () => ConfigureGoogle(app);
}
private void ConfigureGoogle(IAppBuilder app)
{
app.UseGoogleAuthentication(/* stuff */);
}
}
public class HomeController : Controller
{
public event EventHandler Initialized;
[Route("/setup/submit"), AcceptVerbs(HttpVerbs.Post)]
public ActionResult SetupSubmit()
{
/* ... */
Initialized();
}
}
This doesn't throw exceptions, and there are no obvious signs of errors - but it doesn't work because the application delegate has already been composed by this point. Katana doesn't give you any APIs for re-building the application delegate (and I'm not sure that would be a good idea anyway - there would be countless bugs that could be created by such a mechanism; for example, how should the server handle an in-flight request when the application delegate is recomposed after initialization?).
What's your alternative? @DavidFahlander's approach is going to be the right one, but you still need to be careful if you want to achieve dynamism. Look at what .MapWhen does:
// put middleware in pipeline before creating branch
var options = new MapWhenOptions { Predicate = predicate };
IAppBuilder result = app.Use<MapWhenMiddleware>(options);
// create branch and assign to options
IAppBuilder branch = app.New();
configuration(branch);
options.Branch = (AppFunc)branch.Build(typeof(AppFunc));
First, notice that this calls app.Use
with a MapWhenMiddleware
type. This means you are facing the same limitations as before - it all has to be done up front. The branched middleware also will be baked in before initialization is complete: see the last line: branch.Build
.
Your only hope of dynamism here is to use the predicates in a way that achieves your goal. This doesn't get you 100% of the way there, but it gets pretty darn close:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.MapWhen(ctx => ClientHasWsFederationConfigured() && ctx.Request.Headers.Get("Host").Equals("customer1.cloudservice.net"), app2 =>
{
app2.UseWsFederationAuthentication(...);
});
app.MapWhen(ctx => ClientHasGoogleAuthConfigured() && ctx.Request.Headers.Get("Host").Equals("customer2.cloudservice.net"), app2 =>
{
app2.UseGoogleAuthentication(...);
});
}
}
The limitations here will be these:
- You have to configure all your supported authentication types up-front. you can't add a new one while the app is running.
ClientHasXXXConfigured
will be run on every request. This may or may not be acceptable on a performance basis, depending on what you do.
Given the information you put in the question, I think these tradeoffs are probably ok as long as you're careful about what ClientHasXXXConfigured
(or it's equivalent) does.
回答3:
I am working on a Microsoft.Owin.Security design change that will allow for multi-tenancy in AuthenticationOptions (e.g. GoogleAuthenticationOptions) to support the ability for a developer to inject their owin implementations.
Here is my proposal to the Katana project team: https://katanaproject.codeplex.com/discussions/561673
I also have a working implementation that sits on top of the existing Microsoft.Owin.Security infrastructure and does not benefit from my design change suggestion. It requires rolling your own version of the Auth Middleware (copy paste existing) but it's a viable workaround until I can get my design change implemented in Microsoft.
https://github.com/kingdango/Owin.OAuth.Multitenant (This is rough, I just got it up this morning)
Ultimately I don't think it makes sense to develop multiple Owin pipelines for each tenant just to support multi-tenancy. The right solution is to have middleware that is extensible and that's what I'm proposing.
回答4:
I've managed to get this working. The problem is the order of execution of MapWhen doesn't work exactly how one thinks it does.
The key thing to remember is the configuration (i.e. the second parameter of MapWhen) gets executed and cached on app startup. For this reason it is important to think carefully about how many configurations you need and run a seperate 'app.MapWhen' for each unique configuration. If you are using multiple domains and each use the same configuration then you don't need to do this, however if each configuration is unique per domain, you will need to run a MapWhen for each one. In my case I found it easier to put these in a foreach block as I needed to give each domain a unique configuration as OpenIDConnect requires a unique AppID for each one.
The first parameter of MapWhen is a function that returns a conditional. If it evaluates to true it will return the corresponding configuration from a cache as it will already be generated at this point. No new configurations will be generated if this always returned false previously and suddenly returns true. It is important to note that since this conditional function gets executed per request it must be kept as fast and lightweight as possible.
var domains = new string[] { "abc.com", "def.com" };
var host = HttpContext.Current.Request.ServerVariables["HTTP_HOST"]?.ToLower();
foreach (string domain in domains)
{
if (!ShouldEnableForDomain(domain) continue;
app.MapWhen(
context => host == domain, //if true a config will be used from the cache
config =>
{
//This executes once on app startup (per domain) and will be cached - it is not executed in the context of a request
Trace.WriteLine(String.Format("Setting up configuration: {0}", domain));
config.UseOpenIdConnectAuthentication(GetOpenIdOptions(domain));
}
);
}
来源:https://stackoverflow.com/questions/24422903/setup-owin-dynamically-by-domain