mardi 8 janvier 2008

Thread with ASP.NET and Integrated security

Recently, we decided to move to integrated security on all our Dev machines so that we won't need complex security management on the dev db.
Therefore, we used the "impersonate" tag in the web/machine .config as following :
<system.web>
<identity impersonate="true" userName="domain\user" password="password" />

Everything was fine except for the process that created thread for launching parallel DB actions.

Ex of Non working code :

threads.Add(new Thread(new ThreadStart(this.LaunchOneSqlCommand)));



The method "LaunchOneSqlCommand" pops one SQLCommand from the stack of commands to execute and launch them with integrated security specified in the connection string :

public void LaunchOneSqlCommand(object windowsIdentity)

{

SqlCommand com = null;

using (SqlConnection con = new SqlConnection(connectionString))

{

try

{

com = popOne();

if (com != null)

con.Open();

while (com != null)

{

com.Connection = con;

int res = com.ExecuteNonQuery();

...

com = popOne();

}

con.Close();

}

catch (Exception ex)

{
...

throw new MultiThreadedSqlCommandsException(ex.Message);

}

}

...

}



This gave us the terrific error :
Login failed for user ''. The user is not associated with a trusted SQL Server connection.

After some test, we guessed that the thread launched by a Dll under asp.net use the same windowsIdentity as aspnet, and not the one specified with the "impersonate" tag.

Therefore, we had to do some adaptations :

threads.Add(new Thread(new ParameterizedThreadStart(this.LaunchOneSqlCommand)));


And the start must be as following :
for (int i = 0; i < threads.Count; i++)
{
((Thread)thrds[i]).Start(System.Security.Principal.WindowsIdentity.GetCurrent());
}

And the method LaunchOneSqlCommand became

public void LaunchOneSqlCommand(object windowsIdentity)

{

string[] rolesArray = {"role1"};

((WindowsIdentity)windowsIdentity).Impersonate();

Thread.CurrentPrincipal = new System.Security.Principal.GenericPrincipal((IIdentity )windowsIdentity, rolesArray);

...
}

4 commentaires:

Unknown a dit…

Hi,
I know this is long time back article. Right now I'm facing the same problem but running under Application Pool - the data is not being stored properly - the data is mixed when multiple users are running the application.

- Krishna

JMG a dit…

Hi,

I guess the problem comes from the fact that the users all call the same data storage routine at the same time.

If this is the case, the data storage call should be in a "lock" statement :Lock on msdn

The example provided on the msdn is quite straightforward.

As you have several users working with the same function, they may call it at the same time.
Usual example :

void someThing(string userName){
StreamWriter sw = System.IO.File.AppendText(logfile);
sw.WriteLine(logfile,string.Format("User {0}:This is line 1",userName));
...
sw.WriteLine(logfile,string.Format("User {0}:This is line 2",userName));
...
sw.WriteLine(logfile,string.Format("User {0}:This is line 3",userName));
...
}

The logfile content with several users could then be
User : John:This is line 1
User : Rebecca:This is line 1
User : John:This is line 2
User : Itay:This is line 1
User : Fred:This is line 1
User : Rebecca:This is line 2
User : Rebecca:This is line 3
User : Fred:This is line 2
User : John:This is line 3
User : Itay:This is line 2
User : Itay:This is line 3
User : Fred:This is line 3

Quite a mess.

Solution 1:
void someThing(string userName)
{
lock (this)
{
StreamWriter sw = System.IO.File.AppendText(logfile);
sw.WriteLine(logfile, string.Format("User {0}:This is line 1", userName));
//...
sw.WriteLine(logfile, string.Format("User {0}:This is line 2", userName));
//...
sw.WriteLine(logfile, string.Format("User {0}:This is line 3", userName));
//...
sw.Close();
}
}
Not the best as if the code under the ... takes a long time, this time will be multiplied by the number of users so the last one may wait quite a long time.

Solution 2 :
void someThing(string userName)
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("User {0}:This is line 1", userName);
sb.AppendLine();
//...
sb.AppendFormat("User {0}:This is line 2", userName);
sb.AppendLine();
//...
sb.AppendFormat("User {0}:This is line 3", userName);
sb.AppendLine();
//...
lock (this)
{
StreamWriter sw = System.IO.File.AppendText(logfile);
sw.Write(sb.ToString());
sw.Close();
//In this situation, you could use
//System.IO.File.AppendAllText(logfile,sb.ToString());
}
}


To resume, you should prepare the data to store and then put the store instructions within a "lock".

You may also check how many worker process are allowed under your application pool.
As this "lock" technique won't work over several worker processes.

Hope this helps,

JM.

Unknown a dit…

JMG,
Thank you for the prompt reply. I implemented the Lock mechanism and as you said I'm feeling that it is not quite good in web application. I'm trying to view the other options. Also, the data is being stored to the database is completely different for each user and somehow it got messed up when multiple users are working.

Thanks again.

JMG a dit…

Then maybe this is more linked with the database and you should use transactions when storing data.

If you give me more details, I may help...

JM.