【问题标题】:Getting Parameter Size error for Oracle stored procedure C# .NET获取 Oracle 存储过程 C# .NET 的参数大小错误
【发布时间】:2022-01-17 13:20:47
【问题描述】:

我是 PLSQL 的新手,不知道如何从 Oracle 存储过程中获取价值。我该如何解决这个问题?

System.Exception:'参数'结果':没有为可变长度数据类型设置大小:字符串。'

我正在使用 Iracle 10g

程序:

PROCEDURE LoginAuthentication(uid in users.userid%type,pass in users.password%type,result out users.role%type)
    is
    rl users.role%type;
    c number;
    begin
    
    select role into rl from users where userid=uid and password=pass and status=1;
    select count(*) into c from users where userid=uid and password=pass and status=1;
    if(c=1) then
    result:=rl;
    else
    result:='null';
    end if;
    exception
     when no_data_found then
     result:='null';
    end LoginAuthentication;

代码:

public string Login(String id, string password)
{
    using (OracleConnection oCon = new OracleConnection("DATA SOURCE = localhost:1521;USER ID = PROJECTDB;Password=pass"))
    {
        OracleCommand Cmd = new OracleCommand();
            
        try 
        {
            Cmd.Connection = oCon;
            Cmd.CommandText = " user_package.LoginAuthentication";
            Cmd.CommandType = CommandType.StoredProcedure;

            Cmd.Parameters.Add("uid", OracleType.VarChar).Value = id;
            Cmd.Parameters.Add("pass", OracleType.VarChar).Value = password;
            Cmd.Parameters.Add("result", OracleType.VarChar).Direction = ParameterDirection.Output;

            oCon.Open();
            Cmd.ExecuteNonQuery();

            string result = Cmd.Parameters["result"].Value.ToString();

            return result;
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally 
        {
            oCon.Close();
        }
    }
}

错误:

System.Exception:'参数'结果':没有为可变长度数据类型设置大小:字符串。

【问题讨论】:

  • 错误说明了问题所在(您需要指定参数大小,而不仅仅是值),但更大的问题是不安全的密码代码。密码永远不应该以明文形式存储,它们应该被加盐和散列多次(至少 1000 次)。而null 这个词与空值不同。想象一下,如果有人创建了一个名为 null 的角色会发生什么。
  • 自 2002 年以来的所有 .NET 堆栈都具有安全身份验证和密码存储功能。如果可以的话,不要自己动手。如果您绝对必须(为什么?)使用Rfc2898DeriveBytes 类使用加密强算法对密码进行哈希处理。就像`k1 = new Rfc2898DeriveBytes(pwd1, salt1, myIterations);`然后调用var hash=k1.GetBytes(20);

标签: c# .net oracle stored-procedures plsql


【解决方案1】:

错误说明了问题所在 - 您需要指定参数大小。在 Oracle 和 SQL Server 中,文本参数需要一个大小。对于uidpass,驱动程序可以假设参数大小等于值的大小(这可能导致值被截断),但是对于输出参数,它无法知道要使用什么大小。

你可以只传递大小,但是 ....

Cmd.Parameters.Add("result", OracleType.VarChar,20)...

一个更大的问题是不安全的密码。密码永远不应该以明文形式存储,它们应该被多次加盐和散列(至少 1000 次)。

而且 null 这个词与 null 值不同。想象一下如果有人创建了一个名为"null" 的角色会发生什么。这不是吹毛求疵——系统就是这样被破解的。

更安全的实施

可追溯到 2002 年的所有 .NET 堆栈都具有安全身份验证和密码存储功能。如果可以,请不要自己动手。

如果您绝对必须(为什么?)使用Rfc2898DeriveBytes 类使用加密强算法对密码进行哈希处理。

很简单:

var keygen = new Rfc2898DeriveBytes(pass, salt,1000);
var hash=k1.GetBytes(20);

并使用该哈希而不是密码。 salt 可以是任意随机值。通常,这与哈希一起存储,以确保每个用户帐户都有不同的盐。很多时候,盐和密码组合在一起并存储在一个字段中。存储新密码时,您可以计算新的盐和哈希值并将它们存储在一起,例如在 binary(28) 字段中:

byte[] salt = new byte[8];
using (var rngCsp = new
RNGCryptoServiceProvider())
{
    rngCsp.GetBytes(salt);
}
var keygen = new Rfc2898DeriveBytes(pass, salt,1000);
var hash=keygen.GetBytes(20);

var finalHash=new byte[28];
Array.Copy(salt,finalHash,8);
Array.Copy(hash,finalHash,8,20);
...
//Store the hash

密码检查代码也很少使用COUNT(*) 查询。帐户的 salt 和 hash 从数据库中加载,根据用户输入计算新的 hash,然后比较两个 hash。

数据库代码也可以简化。 usingfinally 更安全,因为finally 可以在某些灾难性异常中被跳过,而using 不会。您也可以使用 Dapper 之类的库来消除样板代码。

您的代码可以简化为:

using (var oCon = new OracleConnection("DATA SOURCE = localhost:1521;USER ID = PROJECTDB;Password=pass"))
{
    var sql="select hash where userid=:id and status=1";
    var accountHash=oCon.SingleOrDefault<byte[]>(sql,new {id=id});


    if(accountHash == null)
    {
        //No such user
    }
    var salt=accountHash[0..8];
    var storedHash=accountHash[8..];

    var keygen = new Rfc2898DeriveBytes(pass, salt,1000);
    var inputHash=keygen.GetBytes(20);

    if(!inputHash.SequenceEqual(storedHash))
    {
        //Bad password
    }
}

不过,使用堆栈的内置密码存储功能会更好。

也可以修改查询以返回角色。 Dapper 可以将查询结果映射到对象,因此 Account 类或记录定义如下:

record Account(byte[] Hash,string Role);

可以在查询中使用:

var sql="select Hash,Role where userid=:id and status=1";
var account=oCon.SingleOrDefault<Account>(sql,new {id=id});

【讨论】:

    【解决方案2】:

    Panagiotis Kanavos 已经解释了错误并且您应该将密码存储为加盐哈希。您还可以改进您的数据库代码,以消除对 COUNT 行的冗余查询作为您的第一个查询:

    select role
    into   rl
    from   users
    where userid=uid and password=pass and status=1;
    

    如果没有匹配的行,将失败并出现NO_DATA_FOUND 异常;如果有重复的行,则会出现TOO_MANY_ROWS 异常,因此您的第二个查询:

    select count(*)
    into   c
    from   users
    where  userid=uid and password=pass and status=1;
    

    只会返回 1 的值。

    因此,您的代码可以简化为:

    PROCEDURE LoginAuthentication(
      uid    IN  users.userid%type,
      pass   IN  users.password%type,
      result OUT users.role%type
    )
    IS
    BEGIN
      SELECT role
      INTO   result
      FROM   users
      WHERE  userid   = uid
      AND    password = pass
      AND    status   = 1;
    EXCEPTION
      WHEN no_data_found THEN
        result := 'null';
    END LoginAuthentication;
    

    此外,您的错误处理块会将result 设置为字符串文字'null'。您可能不想使用字符串文字,而只想要一个 NULL 值。

    EXCEPTION
      WHEN no_data_found THEN
        result := NULL;
    END LoginAuthentication;
    

    【讨论】:

      猜你喜欢
      • 2015-10-01
      • 1970-01-01
      • 2010-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-03
      • 2014-07-07
      • 1970-01-01
      相关资源
      最近更新 更多