绕过控制器中的模型验证?

时间:2013-10-10 08:10:21

标签: c# asp.net-mvc entity-framework asp.net-mvc-4

我创建了一个数据库的ADO.NET模型。 使用CRUD(实体框架并使用我创建的ADO.NET实体模型)创建了一个新控制器。

在我的数据库中,我有一个简单的Users表。表中的密码行将包含使用SimpleCrypto(PBKDF2)加密的用户密码。

在我的ADO.NET Users.cs模型中,我添加了以下验证:

[Required]
[DataType(DataType.Password)]
[StringLength(20, MinimumLength = 6)]
[Display(Name = "Password")]
public string Password { get; set; }

这与浏览器中的jQuery一起使用验证。 但是在我的控制器中,我正在加密密码,然后密码字符串将长度超过20个字符。

var crypto = new SimpleCrypto.PBKDF2();
var encryptedPass = crypto.Compute(user.Password);

user.Password = encryptedPass;
user.PasswordSalt = crypto.Salt;

_db.Users.Add(user);
_db.SaveChanges();

这给了我和“一个或多个实体的验证失败。” - 错误。

我可以将用户复制到“var newUser”,然后在那里设置所有属性,但在这种情况下是不是更容易绕过模型验证?

编辑:如果我删除了模型中密码道具的验证,那么一切正常。因此,由于控制器中的加密,我将密码从6-20长度字符更改为+100 lengt字符,因此验证会给出错误。

编辑:插入此问题的完整控制器部分。

[HttpPost]
public ActionResult Create(Users user)
{
    if (!ModelState.IsValid)
    {
        return View();
    }
    if (_db.Users.FirstOrDefault(u => u.Email == user.Email) != null)
    {
        ModelState.AddModelError("", "User already exists in database!");
        return View();
    }

    var crypto = new SimpleCrypto.PBKDF2();
    var encryptedPass = crypto.Compute(user.Password);

    user.Password = encryptedPass;
    user.PasswordSalt = crypto.Salt;

    _db.Users.Add(user);
    _db.SaveChanges();

    return RedirectToAction("Index", "User");
}

3 个答案:

答案 0 :(得分:8)

这是ViewModels进入游戏的地方。您应该创建一个传递给视图的模型,然后将其映射回域模型。

ViewModel:

public class RegisterModel
{
    [Required]
    public string UserName { get; set; }

    [Required]
    [DataType(DataType.Password)]
    [StringLength(20, MinimumLength = 6)]
    [Display(Name = "Password")]
    public string Password { get; set; }
}

域模型(您当前的User模型):

public class User
{
    // other properties..

    [Required]
    public string Password { get; set; }
}

您可以在控制器中使用这些模型,如下所示:

GET行动:

public ActionResult Register()
{
    var registerModel = new RegisterModel();
    return View(registerModel)
}

有这样的观点:

@model RegisterModel

@Html.LabelFor(model => model.UserName)
@Html.TextBoxFor(model => model.UserName)
@Html.ValidationMessageFor(model => model.UserName)

@Html.LabelFor(model => model.Password)
@Html.PasswordFor(model => model.Password)
@Html.ValidationMessageFor(model => model.Password)

POST行动:

[HttpPost]
public ActionResult Register(RegisterModel registerModel)
{
    // Map RegisterModel to a User model.       
    var user = new User
                   {
                        UserName = registerModel.UserName,
                        Password = registerModel.Password   // Do the hasing here for example.
                    };
    db.Users.Add(user);
    db.SaveChanges();                           
}

答案 1 :(得分:3)

您说您的密码加密正在控制器中进行。在这种情况下,你不应该在验证后加密吗?例如:

public ActionResult SomeControllerAction(UserViewModel user)
{
    if (!ModelState.IsValid)
    {
        // at this point the human readable (and assuming < 20 length) password
        // would be getting validated
        return View(user);
    }

    // now when you're writing the record to the DB, encrypt the password
    var crypto = new SimpleCrypto.PBKDF2();
    var encryptedPass = crypto.Compute(user.Password);

    user.Password = encryptedPass;
    user.PasswordSalt = crypto.Salt;

    _db.Users.Add(user);
    _db.SaveChanges();

    // return or redirect to whatever route you need
}

如果您想要专门控制验证,请尝试在视图模型类上实现IValidatableObject并在此处执行验证,而不是通过属性。例如:

public class UserViewModel : IValidatableObject
{
    public string Username { get; set; }
    public string Password { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        // validate the unencrypted password's length to be < 20
        if (this.Password.Length > 20)
        {
            yield return new ValidationResult("Password too long!");
        }
    }        
}

答案 2 :(得分:2)

如果我理解正确,你有一个带密码字段的数据库表 根据您的型号,此密码字段长度为20个字符

[StringLength(20, MinimumLength = 6)]

您想插入一个大于20个字符的值 如果实体框架没有阻止你,那么你会得到一个数据库错误。(实体框架不知道你的数据模型和数据库之间存在不匹配,并且它不想承担推动插入的风险) />
但我假设您在viewmodel上指定了clientSide验证规则,而不是数据库上的长度约束。
我希望你明白为什么这是一个令人困惑的设置。

我的建议是拆分你的viewModel和模型,这样你就可以发布一个viewModel,其密码为maxlength 20,你可以将其转换为长度为100的模型密码。

如果你发现太麻烦,你可以创建一个未映射的密码属性,你在发布时从html设置并将其转换为控制器中的密码属性。
你的课可能是这样的:

public class RegisterModel
{
    [Required]
    public string UserName { get; set; }

    [Required]
    [NotMapped]
    [StringLength(20, MinimumLength = 6)]
    [Display(Name = "Password")]
    public string PlainTextPassword { get; set; }

    [Required]
    [StringLength(300)]//This is optional
    [DataType(DataType.Password)]
    public string Password { get; set; }
}