Log a user in to an ASP.net application using Windows Authentication without using Windows Authentication?
I have an ASP.net application I'm developing authentication for. I am using an existing cookie-based log on system to log users in to the system. The application runs as an anonymous account and then checks the cookie when the user wants to do something restricted. This is working fine.
However, there is one caveat: I've been told that for each page that connects to our SQL server, I need to make it so that the user connects using an Active Directory account. because the system I'm using is cookie based, the user isn't logged in to Active Directory. Therefore, I use impersona开发者_Python百科tion to connect to the server as a specific account.
However, the powers that be here don't like impersonation; they say that it clutters up the code. I agree, but I've found no way around this. It seems that the only way that a user can be logged in to an ASP.net application is by either connecting with Internet Explorer from a machine where the user is logged in with their Active Directory account or by typing an Active Directory username and password. Neither of these two are workable in my application.
I think it would be nice if I could make it so that when a user logs in and receives the cookie (which actually comes from a separate log on application, by the way), there could be some code run which tells the application to perform all network operations as the user's Active Directory account, just as if they had typed an Active Directory username and password.
It seems like this ought to be possible somehow, but the solution evades me. How can I make this work?
Update To those who have responded so far, I apologize for the confusion I have caused. The responses I've received indicate that you've misunderstood the question, so please allow me to clarify.
I have no control over the requirement that users must perform network operations (such as SQL queries) using Active Directory accounts. I've been told several times (online and in meat-space) that this is an unusual requirement and possibly bad practice.
I also have no control over the requirement that users must log in using the existing cookie-based log on application. I understand that in an ideal MS ecosystem, I would simply dis-allow anonymous access in my IIS settings and users would log in using Windows Authentication. This is not the case. The current system is that as far as IIS is concerned, the user logs in anonymously (even though they supply credentials which result in the issuance of a cookie) and we must programmatically check the cookie to see if the user has access to any restricted resources.
In times past, we have simply used a single SQL account to perform all queries. My direct supervisor (who has many years of experience with this sort of thing) wants to change this. He says that if each user has his own AD account to perform SQL queries, it gives us more of a trail to follow if someone tries to do something wrong.
The closest thing I've managed to come up with is using WIF to give the user a claim to a specific Active Directory account, but I still have to use impersonation because even still, the ASP.net process presents anonymous credentials to the SQL server.
It boils down to this: Can I log users in with Active Directory accounts in my ASP.net application without having the users manually enter their AD credentials? (Windows Authentication)
Update June 1: I tried replacing the HttpContext.Current.User
Principal with one constructed from an identity returned by C2WTS
in global.asax. global.asax seems fine, but after the authentication method runs, the following exception is thrown:
[UnauthorizedAccessException: Attempted to perform an unauthorized operation.] System.Security.Principal.WindowsIdentity.get_AuthenticationType() +2176525 Microsoft.IdentityModel.Claims.WindowsClaimsPrincipal..ctor(WindowsIdentity identity, String issuerName) +82 Microsoft.IdentityModel.Claims.WindowsClaimsPrincipal.CreateFromWindowsIdentity(WindowsIdentity identity, String issuerName) +138 Microsoft.IdentityModel.Claims.ClaimsPrincipal.CreateFromPrincipal(IPrincipal principal, String windowsIssuerName) +225 Microsoft.IdentityModel.Claims.ClaimsPrincipal.CreateFromHttpContext(HttpContext httpContext, Boolean clientCertificateAuthenticationEnabled) +67 Microsoft.IdentityModel.Web.WSFederationAuthenticationModule.OnPostAuthenticateRequest(Object sender, EventArgs e) +45 System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +68 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +75
Thanks again to everyone helping out with this.
Update again
After giving this some more thought, I found this page: http://www.leastprivilege.com/GenevaHTTPModulesClaimsPrincipalHttpModule.aspx. The author points out that there is a class in WIF that I'm not yet familiar with called WindowsClaimsIdentity
which seems to be a hybrid Windows/Claims Identity. I'm trying to find how to set up my impersonation using this class. In order to set this identity when the user logs on, I'm using the ClaimsAuthenticationManager
class. I think I'm getting closer to a solution.
Update again
In response to the latest answer, I have successfully created the WindowsPrincipal
object. I successfully used the WindowsPrincipal
to impersonate in code. However, doing the impersonation from web.config
(which is what the boss wants) doesn't work. I have the problem narrowed down some more.
There are three places you can retrieve the logged-in user from:
HttpContext.Current.User
and Thread.CurrentPrincipal
both return the identity down-castable to a Windows Principal. However, WindowsPrincipal.GetCurrent
still returns the account specified in the "Anonymous Access" section of the IIS configuration. In the past, I have tried logging users in with Windows Authentication
(which as I said before, I absolutely cannot use with this application) which does cause WindowsPrincipal.GetCurrent
to return the logged-in users account. What must I do to get WindowsPrincipal.GetCurrent
to return the logged-in users account without Windows Authentication
?
Since Rising Star indicated a switch to Windows authentication isn't an option, what about doing impersonation in code? The one caveat is that you will need to know the users password. In fact, I would say that if you don't have access to the users password, impersonating the user is not possible.
I think the WindowsImpersonation class can provide what you need. An example of how to do this with a specific id and password can be found in MSDN.
Original Answer
Can you switch your application from your current authentication method to Windows authentication? In this case, if you enable impersonation without specifying an id/password, your application will impersonate the logged on user which I believe is what you are looking for.
A very thorough explanation of setting this up can be found here.
Have you checked out adding impersonation to the web.config? That way your process doesn't run as an anonymous user, but instead runs as an authenticated network account:
<identity impersonate="true" userName="domain\username" password="goes@here"/>
It's how my company handles matters. Conversely, you could setup the app pool to run as a network user.
It seems like you're trying to use the DB to restrict access to functionality. I'm not sure that's the best path. Usually, the app has full access to the db, and then you restrict access using code.
UPDATE: As per your comment, it seems like the previous paragraph is relevant. I've never heard of an architecture where the ASP.NET framework handles impersonation on a per-logged in basis. I've seen systems where user was authenticated through the underlying network structure - aka kerberos authentication performed by IIS / Windows.
As before, traditionally you don't have every single end-user granted unique rights on the db server. The application has a login to the db, and then individual users are managed through the app.
Is there a reason you're not using kerberos and pushing the workload off to IIS?
At some point, you're going to have to ask the users for credentials - this might be a one time request (or until the user changes their password anyway).
It sounds like you are trying to do Single Sign On - albeit it usually does the opposite (allow you to use your windows account to pass through non windows account credentials to other apps) but theres no reason you couldnt do the following:
- user logs in to app as normal
- if user has no windows credentials stored
- ask the user for credentials
- try to log in as user
- if fail login
- ask the user for credentials again
- store credentials
Once you have the credentials stored in your "SSO" cache you can use them to create new NetworkCredential objects for your network requests (i.e. your SQL connections, File copies etc).
You can read a description here http://aspalliance.com/1545_understanding_single_signon_in_aspnet_20.all
I am still a bit confused with the terminology here. But I think you are in trouble :)
In my opinion/understanding the following two statements already tell you that what you want is impossible:
I've been told that for each page that connects to our SQL server, I need to make it so that the user connects using an Active Directory account.
versus
However, the powers that be here don't like impersonation;
I would say the first statement implies impersonation. I believe this to be a very common requirement by the way. Mainly for exactly the reason you stated: it lets you see who is doing what on your database. It might even be (or become) requested by law... You usually do not have to give permissions to individuals though, I tend to use groups/roles.
Now in your comment to @Mauro you write
I have a cookie which contains the AD account I want to execute the SQL query as. My only question is, how do I make the asp.net process run the SQL query as the identity specified by the cookie?
What do you mean by "contains the AD account"? Just the username or username and password? If you have both you can (must?) follow the MSDN article (WindowsImpersonationContext) mentioned by @Jeff Siver (or is this what you and your management describe as "cluttering the code"?).
If you do not have the password you are out of luck. How would you possibly run something with the privileges of a certain user without providing the system any clue (but a username) that you are allowed to do so? I cannot see at the moment how a cookie could help here. Unless - hmm - maybe you could roll your own Security Principal? That is above me, though.
In case we do not (yet) have the same understanding of some terms: this would be the (or rather my) list of "usual suspects" capable of producing misunderstandings.
- Windows Authentication: the option in IIS vs. Windows Integrated Authentication in SQL Server
- Impersonation: the ASP.NET option (as stated by @JustLoren) vs. impersonation in code ("cluttered code"?)
- Delegation: the passing on of credentials from IIS to SQL Server (for example) vs. triple-hop (passing of credentials from IE (or browser) to IIS to SQL Server; also a sort of delegation)
UPDATE
WIF seems to bring certain complexity, see Figure 7 and Figure 8 in Overview of the Claims Based Architecture which can be found on Windows Identity Foundation Simplifies User Access for Developers. And I have to admit that I do not know WIF. From what I see it might really be able to help you; or it might just "shift the problem"...
BUT from your comment
I have a security principal with the users AD account
I think you might be able to switch the identity of the user "running the web app" (i.e. making the calls to the database) using the mechanism that can also be used to roll your own "security principal-solution".
I am unsure if you then still need the impersonate
in Web.config
as suggested by @JustLoren. But it might well be.
You would start by replacing the current security principal with "your own" (the one you get through C2WTS
) in Global.asax
s Application_AuthenticateRequest
-method (hopefully this will still be called if you allow anonymous access?!?). But beware I would say this is by no means trivial.
You can find more information (and warnings) here:
- System.Threading.Thread.CurrentPrincipal vs. System.Web.HttpContext.Current.User or why FormsAuthentication can be subtle
- Update on my struggles with the ASP.NET Development Server
- Using Custom Identities in ASP.NET fails when using the ASP.NET Developement Server
UPDATE2
So.... You're saying that the answer to my question may be to somehow replace the current security principal in this method?
That is what I meant, yes. Here comes some example code (excerpts of something without WIF I know to work).
Custom Principle Class (which you would not need):
using System;
using System.Collections.Generic;
using System.Web;
using System.Security.Principal;
namespace PrincipalTest
{
[Serializable]
public class MyPrincipal : WindowsPrincipal
{
public MyPrincipal(WindowsIdentity ntIdentity) : base(ntIdentity)
{
}
public bool IsAdmin
{
get
{
bool isAdmin = (string.Compare(this.Identity.Name,
@"<domain>\<user>", StringComparison.OrdinalIgnoreCase)
== 0);
return isAdmin;
}
}
}
}
In Global.asax
(you would do your WIF-magic here):
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
DotnetReportingPrincipal principal =
new MyPrincipal(HttpContext.Current.User.Identity
as WindowsIdentity);
// Attach the new principal object to the current HttpContext object
HttpContext.Current.User = principal;
System.Threading.Thread.CurrentPrincipal
= System.Web.HttpContext.Current.User;
}
You can then do things like this (again not needed in your case I assume):
protected void Page_Load(object sender, EventArgs e)
{
IPrincipal current = HttpContext.Current.User;
if (current is MyPrincipal)
{
if (current as DotnetReportingPrincipal).IsAdmin)
{
...
}
}
}
In your situation I would try (hope :)) you can just set ASP.NET to use impersonation (again as suggested by @JustLoren). Hopefully IIS will then pass the "new" user down to SQL Server for querying.
If this is not the case, make sure your configuration allows for delegation. Here would be some hints:
- MSDN: ASP.NET Delegation
- MS Support: How to configure an ASP.NET application for a delegation scenario
- MSDN: How To: Use Protocol Transition and Constrained Delegation in ASP.NET 2.0
Please remember that I am on very thin ice here. But - who knows...
You might be able to achieve something that has been shown in a Microsoft demo, not bad at all :)
Good luck!
Anotherupdate
Hmm - I am not sure it I like the way things go here ;) I honestly hope I did not point you in the wrong direction...
As you said, there is the WindowsClaimsIdentity
class:
there is a class in WIF that I'm not familiar with called WindowsClaimsIdentity which seems to be a hybrid Windows/Claims Identity. I'm trying to find how to set up my impersonation using this class.
But it says:
If client is using Windows authentication, create a WindowsClaimsPrincipal. This principal allows downcasting to WindowsPrincipal and WindowsIdentity (to access things like impersonation and other Windows security specific features).
And your client is not using Windows authentication, I believe. So there will not be a WindowsPrincipal
; which is bad news.
For your scenario to work you absolutely need a WindowsPrincipal
in the end, I am quite convinced. This is because I believe this to be the only one you can pass to SQL Server. So the question is: how do you get a WindowsPrincipal
from "your cookie"?
The way you are doing thing now you might already get a WindowsPrincpal
by the way; but the wrong one, I fear. Why? Because you do not have <authentication mode="Windows" />
in your Web.config
. Or if you have it and allow anonymous access (which you do) you will end up with the NETWORK SERVICE
or machine account Windows Principal (you could easily check this and tell me if I am wrong though).
By the way: do you have any <authentication mode="...">
in Web.config
? I do not think so!?!
So again: how can you get a WindowsPrincipal
for the current user from the cookie information you have?
Here are my thoughts (I have to admit that they leave a bit of a foul smell in my mouth; it feels like reinventing the (SSO) wheel...).
- Configure your app to allow anonymous access in IIS (this is a requirement, as I understand).
- Set your app to use impersonation (in
Web.config
, see @JustLoren's answer) - Use the code under "Obtain a Windows Token for the Original Caller" in How To: Use Protocol Transition and Constrained Delegation in ASP.NET 2.0 to obtain a
WindowsIdentity
inGlobal.asax
sApplication_AuthenticateRequest
-method (using the username/password information you can hopefully retrieve from "the cookie system"). - Create a
WindowsPrincipal
from thisWindowsIdentity
as described under "To create a WindowsPrincipal object for a single validation", step 2 in How to: Create a WindowsPrincipal Object ; essentially by doingWindowsPrincipal MyPrincipal = new WindowsPrincipal(MyIdentity);
. - Attach you newly created
WindowsPrincipal
to the current HttpContext and thread (as proposed in "Update2"). - Cross your fingers :) But I have certain confidence in this. The shaky part seems to be to get the correct
WindowsIdentity
...
Yetanontherupdate
In section "Impersonate a Specific User in Code" of the article How to implement impersonation in an ASP.NET application still another way to get a WindowsIdentity
from username/password is used: LogonUserA
imported from advapi32.dll
.
Maybefinalupdate
After your latest update I can see clearer now. You are absolutely right with you question. As far as I know, changing (or taking influence on) what WindowsPrincipal.GetCurrent
returns is only possible by activating Windows Authentication and disabling anonymous access in IIS. Which is what you cannot do.
But wait!
Wow, this is getting dirty. But as I saw the tag "kludge" on your question I dare write the following... But beware: I am pretty sure this is not what Microsoft showed in this demo you mentioned :) Neither if this has any unwanted side effects!
What you should be able to do (my quick and dirty test showed it seems to work) is
- Add a
System.Security.Principal.WindowsImpersonationContext
field inGlobal.asax
- In
Global.asax
sApplication_AuthenticateRequest
get aWindowsIdentity
for the user you want to impersonate (you seem to be able to do this as you could impersonate the user in code) - Still in
Global.asax
sApplication_AuthenticateRequest
impersonate the user (in code as you did before or as described in the article(s) cited above; e.g. here), do not callimpersonationContext.Undo()
... - In
Global.asax
sApplication_EndRequest
callimpersonationContext.Undo()
on the field you created two steps before - Keep
<identity impersonate="true" />
inWeb.config
- Cross all your fingers and toes :)
True, you would then still impersonate the user in code (i.e. "clutter your code with impersonation") but only slightly :) Meaning only in one place (Global.asax
).
What do you reckon?
@Rising Star: by the way: in your comment from 2010-05-31 14:03:31Z
you write "He says that you can uncheck that box and then...". Is this by intention or did you mean to write "check"? Because if you uncheck that box you have to check something else to still allow access to anyone. What would you (or he) check instead? Forms Authentication (as there is no "magic flute authentication" as you correctly noted here :))? Sorry, I (automatically) misread it as "check" until now...
Update (by Rising Star) I see you've converted this thread to community Wiki so I no longer have to manually update the question. This is going to be a mess, but once the problem is solved, we can clean it up a bit.
scherand asked how I attach a Windows Identity
to a cookie. This is done using the WIF framework; specifically, I do this by returning a WindowsClaimsPrincipal
from the ClaimsAuthenticationManager
as described in the "least privilege" link that I provided. Also, yes, the boss did say to uncheck the "anonymous access" box in the IIS settings; that seems to break everything.
I think you might be on the right track with your answer. I was just looking into doing my own custom code in global.asax myself.
You should configure your site to use a service account specific to the site, this would have access to the database. This has no bearing on the accounts used to access the site. The two are separate, no impersonation should be necessary to meet requirements.
The problem here is your assumption, and the assumption of most (if not all replies) that the AD account which accesses SQL Server must be an account representing the authenticating user.
Enabling impersonation only creates problems to be solved.
My opening statement is how most Site-->DB access is secured, additional (not-replacing) mechanisms include IPSec, Secure Tunnels, etc. First order of business is to allocate a 'service account' (and AD account to be used by the Site to secure communications not just with MSSQL, but ALL networked resources.)
精彩评论