【发布时间】:2020-02-07 14:35:32
【问题描述】:
我正在编写 xunit 来测试 Authenticate 方法。这很简单:
public User Authenticate(string username, string password)
{
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
return null;
var user = _context.Users.SingleOrDefault(x => x.Username == username);
// check if username exists
if (user == null)
return null;
// check if password is correct
if (!VerifyPasswordHash(password, user.PasswordHash, user.PasswordSalt))
return null;
// authentication successful
return user;
}
VerifyPasswordHash方法:
private static bool VerifyPasswordHash(string password, byte[] storedHash, byte[] storedSalt)
{
if (password == null) throw new ArgumentNullException("password");
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "password");
if (storedHash.Length != 64) throw new ArgumentException("Invalid length of password hash (64 bytes expected).", "passwordHash");
if (storedSalt.Length != 128) throw new ArgumentException("Invalid length of password salt (128 bytes expected).", "passwordHash");
using (var hmac = new System.Security.Cryptography.HMACSHA512(storedSalt))
{
var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
for (int i = 0; i < computedHash.Length; i++)
{
if (computedHash[i] != storedHash[i]) return false;
}
}
return true;
}
但为了测试这一点,我需要在我的数据库中植入一些 User 实体。
这就是我想要做的:
public void TestAuthenticate()
{
//Arrange
var options = new DbContextOptionsBuilder<DataContext>() //instead of mocking we use inMemoryDatabase.
.UseInMemoryDatabase(databaseName: "TestAuthenticate")
.Options;
var config = new MapperConfiguration(cfg =>
cfg.AddProfile<AutoMapperProfile>());
var mapper = config.CreateMapper();
var fakeUser = new User()
{
Username = "anon1", FirstName = "fakename", LastName = "fakelastname", Role = "admin", PasswordHash = null, PasswordSalt = null
};
using (var context = new DataContext(options))
{
context.Users.Add(fakeUser);
context.SaveChanges();
}
// Act
using (var context = new DataContext(options))
{
var service = new UserService(context, mapper);
var result = service.Authenticate(fakeUser.Username, "somepassword");
// Assert
Assert.IsType<User>(result);
}
}
我这里把PasswordHash和PasswordSalt设为null,但是他们应该是byte[],这就是他们在数据库中的存储方式:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Username { get; set; }
public byte[] PasswordHash { get; set; }
public byte[] PasswordSalt { get; set; }
public string Role { get; set; }
}
如果您发现整体测试逻辑很奇怪,请告诉我如何进行此测试并留下一些反馈。这是我第一次尝试编写单元测试。
【问题讨论】:
-
要么获取您的应用程序用于散列的 Salt 值和使用的加密算法,以便您可以获取
PasswordSalt和PasswordHash的字节数组,或者不要调用VerifyPasswordHash()。没有看到VerifyPasswordHash做了什么,很难提供比我评论更多的帮助。这篇文章展示了加盐和散列的工作原理 (stackoverflow.com/questions/2138429/…) -
那么,
passwordSalt和passwordHash如何在单元测试的外部 填充?我假设给定一个密码和一个(随机)盐,计算一个散列,并存储盐和散列。为什么不能在单元测试中这样做? -
您好,谢谢。我添加了 VerifyPasswordHash 方法。我正在考虑在用户注册时将创建Hash和Salt的方法添加到数据库中,但真的不想让这个问题看起来太复杂。
-
恭喜。这是我在 LOOOONG 时间内看到的第一个问题,以实际展示对正确密码处理的理解。为此 +1。
-
我们需要将问题中的代码复制/粘贴到我们的答案中是很常见的。如果您发布图像,我们必须重新输入。这通常足以让某人完全跳过问题并继续下一个问题。粘贴代码文本而不是制作图像也减少了工作量。最后,一小部分用户甚至看不到图像。
标签: c# arrays unit-testing asp.net-core xunit