简体   繁体   English

获取 Oracle 存储过程 C# .NET 的参数大小错误

[英]Getting Parameter Size error for Oracle stored procedure C# .NET

I'm new to PLSQL and can't figure out how to restive value from Oracle stored procedures.我是 PLSQL 的新手,不知道如何从 Oracle 存储过程中获取值。 How can I solve this?我该如何解决这个问题?

System.Exception: 'Parameter 'result': No size set for variable length data type: String.' System.Exception:'参数'结果':没有为可变长度数据类型设置大小:字符串。'

I'm using Iracle 10g我正在使用 Iracle 10g

Procedure:程序:

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;

Code:代码:

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();
        }
    }
}

Error:错误:

System.Exception: 'Parameter 'result': No size set for variable length data type: String. System.Exception:'参数'结果':没有为可变长度数据类型设置大小:字符串。

The error explains what's wrong - you need to specify the parameter size.错误说明了问题所在 - 您需要指定参数大小。 In Oracle and SQL Server text parameters require a size.在 Oracle 和 SQL 服务器文本参数中需要大小。 For uid and pass the driver can assume the parameter size is equal to the value's size (which can result in truncated values), but with the output parameter, it has no way of knowing what size to use.对于uidpass ,驱动程序可以假设参数大小等于值的大小(这可能导致值被截断),但是使用 output 参数,它无法知道使用什么大小。

You could just pass the size but....你可以通过大小但是....

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

A far bigger problem though is the insecure password code.一个更大的问题是不安全的密码代码。 Passwords should never be stored as clear text, they should be salted and hashed multiple times (1000 at least).密码永远不应该以明文形式存储,它们应该被多次加盐和散列(至少 1000 次)。

And the word null isn't the same as the null value. null 这个词与 null 的值不同。 Imagine what would happen if someone created a role named "null" .想象一下如果有人创建了一个名为"null"的角色会发生什么。 This isn't nitpicking - systems have been cracked in just this way.这不是吹毛求疵——系统就是以这种方式被破解的。

A more secure implementation更安全的实施

All .NET stacks going back to 2002 have a secure authentication and password storage feature.可追溯到 2002 年的所有 .NET 堆栈都具有安全身份验证和密码存储功能。 Don't roll your own if you can.如果可以的话,不要自己动手。

If you absolutely have to ( why? ) use the Rfc2898DeriveBytes class to hash passwords using a cryptographically strong algorithm.如果您绝对必须(为什么? )使用Rfc2898DeriveBytes class 到 hash 密码使用加密强算法。

It's as simple as:它很简单:

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

And using that hash instead of a password.并使用 hash 而不是密码。 salt can be any random value. salt可以是任何随机值。 Typically this is stored along with the hash, ensuring every user account has a different salt.通常这与 hash 一起存储,确保每个用户帐户都有不同的盐。 Quite often the salt and password are combined and store in a single field.很多时候,盐和密码组合在一起并存储在一个字段中。 When storing a new password, you could calculate a new salt and hash and store them together eg in a binary(28) field:存储新密码时,您可以计算新盐和 hash 并将它们一起存储在例如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

Password checking code seldom uses a COUNT(*) query too.密码检查代码也很少使用COUNT(*)查询。 The salt and hash for an account are loaded from the database, a new hash is calculated from user input and the two hashes compared.从数据库加载帐户的盐和 hash,根据用户输入计算新的 hash,并比较两个哈希值。

The database code could be simplified too.数据库代码也可以简化。 using is safer than finally , as finally can be skipped in some catastrophic exceptions, while using won't. usingfinally更安全,因为finally可以在某些灾难性异常中被跳过,而using则不会。 You could use a library like Dapper as well to eliminate the boilerplate code.您也可以使用像Dapper这样的库来消除样板代码。

Your code could be simplified to this:您的代码可以简化为:

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
    }
}

It would be even better to use the built-in password storage feature of your stack though.不过,使用堆栈的内置密码存储功能会更好。

The query could be modified to return the role as well.也可以修改查询以返回角色。 Dapper can map query results to objects, so an Account class or record defined like this: Dapper 可以 map 查询结果到对象,所以一个Account class 或记录定义如下:

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

Could be used in the query:可以在查询中使用:

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

this solved my problem,这解决了我的问题,

Cmd.Parameters.Add("result", OracleType.VarChar,20).Direction = ParameterDirection.Output;

Panagiotis Kanavos has explained the error and that you should store the password as a salted hash. Panagiotis Kanavos解释了错误,您应该将密码存储为盐渍 hash。 You can also improve your database code to eliminate the redundant query to COUNT the rows as your first query:您还可以改进您的数据库代码,以消除将COUNT行作为您的第一个查询的冗余查询:

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

Will fail with a NO_DATA_FOUND exception if there is not a matching row or a TOO_MANY_ROWS exception if there are duplicate rows so your second query:如果没有匹配的行,将失败并出现NO_DATA_FOUND异常,如果有重复的行,则会出现TOO_MANY_ROWS异常,因此您的第二个查询:

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

Will only ever return a value of 1 .只会返回值1

Therefore, your code can be simplified to:因此,您的代码可以简化为:

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;

Further, your error-handling block will set the result to the string literal 'null' .此外,您的错误处理块会将result设置为字符串文字'null' You probably don't want to use a string literal and just want a NULL value.您可能不想使用字符串文字,而只想使用NULL值。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM