与MVC5中现有用户数据库的复杂身份验证

Mar*_*cus 7 asp.net authentication wif asp.net-mvc-5 asp.net-identity

我正在将SaaS应用程序从Classic ASP迁移到.NET MVC5,并将使用EF6 Database First.最终用户的登录表单可由每个租户自定义(在他们自己的子域上,但指向同一个Web应用程序).我们希望使用现有的数据库架构和新的身份验证和授权过滤器.

例如,一个租户上的用户可以通过输入他们的名字,姓氏和我们系统生成的代码来登录.另一个租户的用户可以通过输入他们的电子邮件地址和密码来登录.此外,每个租户都有一个单独的管理员登录名,该登录名使用用户名和密码.另一个租户可以对远程AD服务器使用LDAP身份验证.

有自定义身份验证的最佳实践方法吗?

几乎每篇文章都提出了不同的实现方法:简单地设置FormsAuthentication.SetAuthCookie,使用自定义OWIN提供程序,覆盖AuthorizeAttribute等.

在Classic ASP中,我们查询数据库以找出该租户的登录类型,在登录屏幕上显示相应的字段然后在回发后,检查字段是否与数据库中的内容匹配,然后适当地设置会话变量检查每个页面请求.

谢谢

tra*_*max 6

我发现Identity框架在身份验证选项方面非常灵活.看看这段认证码:

var identity = await this.CreateIdentityAsync(applicationUser, DefaultAuthenticationTypes.ApplicationCookie);

authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
Run Code Online (Sandbox Code Playgroud)

这是Identity中工厂认证部分的标准运行,您可以在Web上的每个Identity示例中找到它.如果你仔细观察它是非常灵活的 - 身份验证所需要的只是ApplicationUser框架不关心你得到的对象.

所以在理论上你可以做这样的事情(伪代码,我没有尝试编译这个):

// get user object from the database with whatever conditions you like
// this can be AuthCode which was pre-set on the user object in the db-table
// or some other property
var user = dbContext.Users.Where(u => u.Username == "BillyJoe" && u.Tenant == "ExpensiveClient" && u.AuthCode == "654")

// check user for null 

// check if the password is correct - don't have to do that if you are doing
// super-custom auth.
var isCorrectPassword = await userManager.CheckPasswordAsync(user, "enteredPassword");

if (isCorrectPassword)
{
    // password is correct, time to login
    // this creates ClaimsIdentity object from the ApplicationUser object
    var identity = await this.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

    // now we can set claims on the identity. Claims are stored in cookie and available without
    // querying database
    identity.AddClaim(new Claim("MyApp:TenantName", "ExpensiveClient"));
    identity.AddClaim(new Claim("MyApp:LoginType", "AuthCode"));
    identity.AddClaim(new Claim("MyApp:CanViewProducts", "true"));


    // this tells OWIN that it can set auth cookie when it is time to send 
    // a reply back to the client
    authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
Run Code Online (Sandbox Code Playgroud)

使用此身份验证,您已为用户设置了一些声明 - 它们存储在Cookie中,并且随处可用ClaimsPrincipal.Current.Claims.声明本质上是键值键对的集合,您可以存储任何您喜欢的内容.

我通常通过扩展方法访问用户的声明:

public static String GetTenantName(this ClaimsPrincipal principal)
{
    var tenantClaim = principal.Claims.FirstOrDefault(c => c.Type == "MyApp:TenantName");
    if (tenantClaim != null)
    {
        return tenantClaim.Value;
    }

    throw new ApplicationException("Tenant name is not set. Can not proceed");
}

public static String CanViewProducts(this ClaimsPrincipal principal)
{
    var productClaim = principal.Claims.FirstOrDefault(c => c.Type == "MyApp:CanViewProducts");
    if (productClaim == null)
    {
        return false;
    }

    return productClaim.Value == "true";
}
Run Code Online (Sandbox Code Playgroud)

因此,在您的控制器/视图/业务层中,您始终可以调用ClaimsPrincipal.Current.GetTenantName(),在这种情况下,您将获得"ExpensiveClient".

或者,如果您需要检查是否为用户启用了特定功能,则可以执行此操作

if(ClaimsPrincipal.Current.CanViewProducts())
{
    // display products
}
Run Code Online (Sandbox Code Playgroud)

您可以自行决定如何存储用户属性,但只要您将其设置为Cookie上的声明,它们就可用.

或者,您可以为每个用户向数据库添加声明:

await userManager.AddClaimAsync(user.Id, new Claim("MyApp:TenantName", "ExpensiveClient"));
Run Code Online (Sandbox Code Playgroud)

这将使索赔持续存入数据库.默认情况下,Identity框架在用户登录时会将此声明添加到用户,而无需手动添加.

但要注意,你不能在cookie上设置太多的声明.Cookie具有浏览器设置的4K限制.身份cookie加密的工作方式使编码文本增加了大约1.1,因此您可以拥有大约3.6K的代表声明的文本.我在这里遇到了这个问题

更新

要通过声明控制对控制器的访问,您可以在控制器上使用以下过滤器:

public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
    public string Name { get; private set; }


    public ClaimsAuthorizeAttribute(string name)
    {
        Name = name;
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var user = HttpContext.Current.User as ClaimsPrincipal;
        if (user.HasClaim(Name, Name))
        {
            base.OnAuthorization(filterContext);
        }
        else
        {
            filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary()
            {
                {"controller", "errors"},
                {"action", "Unauthorised"}
            });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在控制器上使用此属性或单独执行此操作:

    [ClaimsAuthorize("Creating Something")]
    public ActionResult CreateSomething()
    {
        return View();
    }
Run Code Online (Sandbox Code Playgroud)

用户需要对其进行"创建内容"声明才能访问此操作,否则他们将被重定向到"未经身份验证"页面.

最近我玩了索赔认证并制作了类似于你的要求的原型应用程序.请查看简单版本:https://github.com/trailmax/ClaimsAuthorisation/tree/SimpleClaims其中声明是为每个用户单独存储的.或者有更复杂的解决方案,其中声明属于角色,当用户登录时,分配给用户的角色声明:https://github.com/trailmax/ClaimsAuthorisation/tree/master