Dynamic database connection using Asp.Net identity

与世无争的帅哥 提交于 2019-12-11 15:29:39

问题


I am working on a multi-tenant application that uses multiple databases. There is one master database that contains user information and then each tenant database also has their own users for that tenant (which are a subset of the users in the master database).

The user will log in which will check the master database, then based on their details (i.e. which tenant they belong to) it will log them into the application using the user details on their tenant database.

I am using the method described in this thread (Dynamic database connection using Asp.net MVC and Identity2) to set the database for UserManager each time because at the point that the application starts it will not know what database to use therefore the following code in "Startup.Auth" would be setting the incorrect database:

app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);

This seems to be working well for most things but one problem I have is with the user getting logged out after the time set in "validateInterval" shown in the code below (this has been set to 20 seconds for testing):

 app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
                // Enables the application to validate the security stamp when the user logs in.
                // This is a security feature which is used when you change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromSeconds(20),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)),                        
                OnApplyRedirect = ctx =>
                {
                    if (!IsAjaxRequest(ctx.Request))
                    {
                        ctx.Response.Redirect(ctx.RedirectUri);
                    }
                }
            }
        });

I think the problem might be because when the code above is called in the "Startup.Auth" file it does not know what database to use however I have not confirmed this.

If I debug the "GenerateUserIdentityAsync" code I can see that it is getting the correct "securityStamp" for the user from the client database which makes me think it is finding the correct database but I cannot work out why it is still logging out the user after the time set for "validateInterval".

Can anyone offer any advice on how this can be resolved or at least possible ways to try and debug what the problem might be?


回答1:


I have experienced the same issue on my multi-tenant ASP.NET MVC app. If your goal is to set an expiration time for the logged-in user just remove the code in CookieAuthenticationProvider and set the ExpireTimeSpan property in the parent CookieAuthenticationOptions.

Your code should be:

app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            ExpireTimeSpan = TimeSpan.FromMinutes(15), //cookie expiration after 15 mins of user inactivity
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider
            {
            }
        });

Hope this helps.




回答2:


Okay this is the full solution I have come up with which partly uses what @jacktric suggested but also allows for validating the security stamp if a users password has been changed elsewhere. Please let me know if anyone can recommend any improvements or see any downfalls in my solution.

I have removed the OnValidateIdentity section from the UseCookieAuthentication section as follows:

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
    LoginPath = new PathString("/Account/Login"),
    Provider = new CookieAuthenticationProvider
    {
        OnApplyRedirect = ctx =>
        {
            if (!IsAjaxRequest(ctx.Request))
            {
                ctx.Response.Redirect(ctx.RedirectUri);
            }
        }
    }
});

I then have the following IActionFilter that is registered in the FilterConfig.cs which checks if the user is logged in (I have parts of the system that can be accessed by anonymous users) and whether the current security stamp matches the one from the database. This check is made every 30 minutes using sessions to find out when the last check was.

public class CheckAuthenticationFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext filterContext)
    {

    }

    public void OnActionExecuted(ActionExecutedContext filterContext)
    {
        try
        {
            // If not a child action, not an ajax request, not a RedirectResult and not a PartialViewResult
            if (!filterContext.IsChildAction
                && !filterContext.HttpContext.Request.IsAjaxRequest()
                && !(filterContext.Result is RedirectResult)
                && !(filterContext.Result is PartialViewResult))
            {
                // Get current ID
                string currentUserId = filterContext.HttpContext.User.Identity.GetUserId();

                // If current user ID exists (i.e. it is not an anonymous function)
                if (!String.IsNullOrEmpty(currentUserId))
                {
                    // Variables
                    var lastValidateIdentityCheck = DateTime.MinValue;
                    var validateInterval = TimeSpan.FromMinutes(30);
                    var securityStampValid = true;

                    // Get instance of userManager   
                    filterContext.HttpContext.GetOwinContext().Get<DbContext>().Database.Connection.ConnectionString = DbContext.GetConnectionString();
                    var userManager = filterContext.HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();

                    // Find current user by ID
                    var currentUser = userManager.FindById(currentUserId);

                    // If "LastValidateIdentityCheck" session exists
                    if (HttpContext.Current.Session["LastValidateIdentityCheck"] != null)
                        DateTime.TryParse(HttpContext.Current.Session["LastValidateIdentityCheck"].ToString(), out lastValidateIdentityCheck);

                    // If first validation or validateInterval has passed
                    if (lastValidateIdentityCheck == DateTime.MinValue || DateTime.Now > lastValidateIdentityCheck.Add(validateInterval))
                    {
                        // Get current security stamp from logged in user
                        var currentSecurityStamp = filterContext.HttpContext.User.GetClaimValue("AspNet.Identity.SecurityStamp");

                        // Set whether security stamp valid
                        securityStampValid = currentUser != null && currentUser.SecurityStamp == currentSecurityStamp;

                        // Set LastValidateIdentityCheck session variable
                        HttpContext.Current.Session["LastValidateIdentityCheck"] = DateTime.Now;
                    }

                    // If current user doesn't exist or security stamp invalid then log them off 
                    if (currentUser == null || !securityStampValid)
                    {
                        filterContext.Result = new RedirectToRouteResult(
                                new RouteValueDictionary { { "Controller", "Account" }, { "Action", "LogOff" }, { "Area", "" } });
                    }
                }
            }
        }
        catch (Exception ex)
        {
            // Log error                
        }
    }
}

I have the following extension methods for getting and updating claims for the logged in user (taken from this post https://stackoverflow.com/a/32112002/1806809):

public static void AddUpdateClaim(this IPrincipal currentPrincipal, string key, string value)
{
    var identity = currentPrincipal.Identity as ClaimsIdentity;
    if (identity == null)
        return;

    // Check for existing claim and remove it
    var existingClaim = identity.FindFirst(key);
    if (existingClaim != null)
        identity.RemoveClaim(existingClaim);

    // Add new claim
    identity.AddClaim(new Claim(key, value));

    // Set connection string - this overrides the default connection string set 
    // on "app.CreatePerOwinContext(DbContext.Create)" in "Startup.Auth.cs"
    HttpContext.Current.GetOwinContext().Get<DbContext>().Database.Connection.ConnectionString = DbContext.GetConnectionString();
    var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
    authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true });
}

public static string GetClaimValue(this IPrincipal currentPrincipal, string key)
{
    var identity = currentPrincipal.Identity as ClaimsIdentity;
    if (identity == null)
        return null;

    var claim = identity.Claims.FirstOrDefault(c => c.Type == key);
    return claim.Value;
}

And finally anywhere that the users password is updated I call the following, this updates the security stamp for the user whose password is being edited and if it is the current logged in users password that is being edited then it updates the securityStamp claim for the current user so that they will not get logged out of their current session the next time the validity check is made:

// Update security stamp
UserManager.UpdateSecurityStamp(user.Id);

// If updating own password
if (GetCurrentUserId() == user.Id)
{
    // Find current user by ID
    var currentUser = UserManager.FindById(user.Id);

    // Update logged in user security stamp (this is so their security stamp matches and they are not signed out the next time validity check is made in CheckAuthenticationFilter.cs)
    User.AddUpdateClaim("AspNet.Identity.SecurityStamp", currentUser.SecurityStamp);
}


来源:https://stackoverflow.com/questions/55126549/dynamic-database-connection-using-asp-net-identity

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!