我正在使用mysql的.net连接器。我去编写了一个用户登录表单代码,这似乎工作正常,但是当我点击登录按钮并提供有效的登录详细信息时,它告诉我我已成功登录然后告诉我它的用户/通行证组合错误。
这里是代码,一切似乎都应该如此。
程序检查散列密码与数据库中输入的密码,这一切都很好用。但由于某种原因,“成功登录”消息框随后显示,“错误的用户名/密码组合”消息框显示。
我一直想弄清楚过去两天我哪里出错了,它让我疯了。也许你们可以看到我搞砸的地方lol
代码:
try
{
MySqlConnection connection = new MySqlConnection(MyConString);
MySqlCommand command = connection.CreateCommand();
MySqlDataReader Reader;
command.CommandText = "select * from users";
try
{
connection.Open();
}
catch (Exception ex)
{
listBox4.Items.Add(ex);
MessageBox.Show("There has been an error connecting to the user database! Please try again later.");
}
Reader = command.ExecuteReader();
while (Reader.Read())
{
if (textBox4.Text == Reader.GetString(2))
{
string haspass= CryptorEngine.Encrypt(textBox5.Text, true);
if (haspass == Reader.GetString(3))
{
MessageBox.Show("Successfully logged in!");
}
else
{
MessageBox.Show("Wrong Unhashed Username/Password Combination");
}
}
else
{
MessageBox.Show("Wrong Username/Password Combination");
}
}
connection.Close();
}
catch (Exception ex)
{
listBox4.Items.Add(ex);
}
答案 0 :(得分:4)
看起来你的DataReader
正在返回多个结果,代码会进入第一个结果的成功路径,然后是第二个结果的不成功路径。
您需要使您的SQL更具体,而不是查询所有用户select * from users
您应该只查询尝试登录的用户的详细信息,例如(伪代码) -
select * from users where username = yourusernamefield
从表单中获取yourusernamefield
值。您需要通过将用户名字段作为参数传递给查询来防止SQL注入。
答案 1 :(得分:3)
这是一种更好的方法:
try
{
MySqlConnection connection = new MySqlConnection(MyConString);
MySqlCommand command = connection.CreateCommand();
MySqlDataReader Reader;
//Change the "username" and "password" to the corresponding names of these columns in your table
command.CommandText = "SELECT * FROM users WHERE username = @username AND password = @password LIMIT 1";
//assuming textbox4 has the username
command.Parameters.AddWithValue("@username", textBox4.Text);
command.Parameters.AddWithValue("@password", CryptorEngine.Encrypt(textBox5.Text, true));
try
{
connection.Open();
}
catch (Exception ex)
{
listBox4.Items.Add(ex);
MessageBox.Show("There has been an error connecting to the user database! Please try again later.");
//you should return here since if there's no connection you can't run the query
}
Reader = command.ExecuteReader();
if(Reader.HasRows){
MessageBox.Show("Successfully logged in!");
}
else
{
MessageBox.Show("Wrong Username/Password Combination");
}
connection.Close();
}
catch (Exception ex)
{
listBox4.Items.Add(ex);
}
这只会返回一行(如果存在)。
答案 2 :(得分:1)
您正在遍历表格中的每个用户,当然会有一个用户的凭据不匹配。
尝试将凭据添加到您的查询中,例如:
command.CommandText = "select * from users where username = ?username AND password = ?password";
IDbDataParameter usernameParameter = _command.CreateParameter();
usernameParameter.ParameterName = "?username";
usernameParameter.Value = username;
command.Parameters.Add(usernameParameter);
IDbDataParameter passwordParameter = _command.CreateParameter();
passwordParameter .ParameterName = "?password";
passwordParameter .Value = password;
command.Parameters.Add(passwordParameter);
然后,如果找到匹配项,则表示凭据正确无误。
顺便说一下,如果在调用connection.Close()
之前抛出异常,可能不会关闭连接,考虑在finally块中添加Close
调用,或者在using块中使用您的连接像这样:
using (MySqlConnection connection = new MySqlConnection(MyConString))
{
...
}
答案 3 :(得分:1)
您正在查询所有用户,并测试所有用户,并在每个用户上发送消息。如果可能,您应该使用用户名select
限制原始where
。
作为观察,在理想情况下,您将在用户记录上使用“salt”,并在哈希中使用它(以防止通过彩虹表或其他非用户特定方法中断)。例如,如果这是我,我会有类似的东西(为了简洁起见使用“dapper”语法):
string name = ... // whichever text.Text
string pw = ...
var row = connection.Query("select Salt, Hash from Users where Username = @name",
new {name}).SingleOrDefault();
bool loggedIn = false;
if(row != null)
{
byte[] salt = row.Salt, hash = row.Hash;
loggedIn = BlobsAreEqual(CryptorEngine.Encrypt(pw, salt, true), hash);
}
// maybe add random wait here, to slow down brute-force
if(loggedIn) {
// great!
} else {
// increment failed counter, and potentially lock out account
}
答案 4 :(得分:0)
作为一个直接的解决方案,您应该在成功的消息后立即返回。似乎正在发生的是您遍历用户,首先遇到正确的用户帐户,然后在下一次迭代中遇到另一个用户。
要正确解决问题,您可以使用以下代码替换代码:
try
{
using(MySqlConnection connection = new MySqlConnection(MyConString))
using(MySqlCommand command = connection.CreateCommand())
{
command.CommandText =
@"SELECT CASE WHEN EXISTS (
SELECT * from users
WHERE USERNAME = @Username AND Password = @Password
LIMIT 1) THEN 1 ELSE 0 END";
try { connection.Open(); }
catch (Exception ex)
{
listBox4.Items.Add(ex);
MessageBox.Show("There has been an error connecting to the user database! Please try again later.");
}
command.Parameters.AddWithValue("@Username", textBox4.Text);
command.Parameters.AddWithValue("@Password",
CryptorEngine.Encrypt(textBox5.Text, true));
Successful = (int) command.ExecuteScalar() == 1;
MessageBox.Show(Successful
? "Successfully logged in!"
: "Wrong Username/Password Combination");
}
}
catch (Exception ex)
{
listBox4.Items.Add(ex);
}
上面的代码将查询限制为单个结果,并允许您使用连接器的参数化工具来避免潜在的SQL注入攻击并快速断言用户的身份。
EXISTS回答给定子查询是否返回结果。我使用它来确保查询尽可能优化,并避免表扫描。 EXISTS
将忽略子查询中的选择列表,并且一旦确定可以获得给定子查询的任何结果,就会立即返回。 LIMIT
在这个例子中可能是多余的,但我不完全确定。
答案 5 :(得分:0)
这两行:
command.CommandText = "select * from users";
while (Reader.Read())
{
}
您正在从数据库中读取所有用户,并循环显示所有返回的项目。然后你有:
if (textBox4.Text == Reader.GetString(2))
{
....
}
else
{
MessageBox.Show("Wrong Username/Password Combination");
}
使用此代码,您将为每个不是您在输入框中输入的用户打印出“错误的用户名/密码”消息。
您可以只查找输入表单的特定用户:
command.CommandText = "select * from users where username = @username";
command.Parameters.AddWithValue("@username", textBox4.Text);
或只打印一条错误消息:
bool loggedIn = false;
while (Reader.Read())
{
if (textBox4.Text == Reader.GetString(2))
{
string haspass= CryptorEngine.Encrypt(textBox5.Text, true);
if (haspass == Reader.GetString(3))
{
loggedIn = true;
}
}
}
if (loggedIn)
{
MessageBox.Show("Successfully logged in!");
}
else
{
MessageBox.Show("Wrong Unhashed Username/Password Combination");
}
虽然你可以更好地构建它。
事实上,如果您同时发送用户名和密码,您可以一次性完成检查。如果没有该名称的用户或密码不正确,则不会返回任何结果,但如果组合正确,则只有一个结果,您将知道该用户无效而无需进一步操作检查。
答案 6 :(得分:0)
在代码中,您有:command.CommandText = "select * from users"
因此对于您尝试登录的数据库中的每个用户。 Probaly有2个用户,您的登录数据可以用于其中一个用户。您应该在成功之后从函数返回,并且仅在没有用户成功时显示失败。
答案 7 :(得分:0)
循环遍历所有用户。凭证与第一个凭证匹配,您可以“登录”,下一个凭据失败。