ASP.Net MVC Custom Authentication
I have an Asp.Net MVC webapplication sitting inside a website still largely managed by delphi. Security is currently managed by delphi which creates cookies.
It has been decided to authenticate users within the ASP.Net application by extracting the cookie details and passing them to an imported Delphi DLL which returns true or false depending on whether the user is valid.
My plan was to use Forms authentication, but instead of redirecting the user to a form instead call the delphi wrapper and if successful, redirect the user to the original url. This gives the benefit that when security is migrated to .Net, the authentication framework will already exist, just the implementation will need to change.
public ActionResult LogOn(SecurityCookies model, string returnUrl)
{
try
{
if (model != null)
{
Lo开发者_JS百科g.DebugFormat("User login: Id:{0}, Key:{1}", model.UserId, model.Key);
if (authenticator.UserValid(model.UserId, model.Key, 0))
{
FormsService.SignIn(model.UserId, false);
return Redirect(returnUrl);
}
}
...
Note that the SecurityCookies are generated by a custom binding class from the delphi generated cookie - this works well.
The call to the delphi dll also works ok.
The issue I have to overcome is that nearly all of the calls to the .Net application are ajax requests. However when the user is not logged in, the browser makes 3 calls due to the redirects: 1) Original ajax request 2) Redirect to ~/Account/Logon (code above) 3) Redirect back to original ajax request
Although tracking the responses posted back to the client, show that Step 3 returns the correct data, overall the process fails for an as yet undetermined reason. Simply clicking refresh on the client works because now the user is authenticated and the redirect to ~/account/Logon doesn't occur.
Note my client jQuery code is as follows: $.getJSON(requestString, function (data) { //do something with the data });
Is there a way of changing the Forms Authentication process so that instead of redirecting to a Url when the User is not authenticated, I can run some other code instead? I'd like the fact that authentication has taken place to be completely invisible to the User's browser.
If you want to authenticate the request, the place to do this is in global.asax.cs by defining the Application_AuthenticateRequest method. Here you can read out the custom cookies with the imported delphi dll and set the Context.User. All authorization in asp.net is based on the user that is set in the HttpContext. An example of an implementation of the Application_AuthenticateRequest method:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if(authCookie != null)
{
//Extract the forms authentication cookie
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
// Create an Identity object
//CustomIdentity implements System.Web.Security.IIdentity
CustomIdentity id = GetUserIdentity(authTicket.Name);
//CustomPrincipal implements System.Web.Security.IPrincipal
CustomPrincipal newUser = new CustomPrincipal();
Context.User = newUser;
}
}
If the cookie is not valid then you won't set a user in the context.
You can then create a BaseController which all your controllers will inherit from that checks if the user that is provided in the context is authenticated. If the user is not authenticated you can return a HttpUnauthorizedResult.
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (User == null || (User != null && !User.Identity.IsAuthenticated))
{
filterContext.Result = new HttpUnauthorizedResult();
}
else
{
// Call the base
base.OnActionExecuting(filterContext);
}
}
}
And in your web.config:
<authentication mode="None"/>
because you don't want the request to be redirected to a login page.
I'm going to answer a part of this specific question
Is there a way of changing the Forms Authentication process so that instead of redirecting to a Url when the User is not authenticated, I can run some other code instead? I'd like the fact that authentication has taken place to be completely invisible to the User's browser.
No there is not. I wrote a login system that is similar to yours being that it is built ontop of forms authentication and the fact there is no extensibility point made it a complete PITA to write.
The way I was able to override the built in behavior was using a HttpModule that monitors for redirects to the forms auth URL and then to intercept that action and lets me handle it.
internal static bool IsFormsAuthenticationLogin(HttpResponseBase response)
{
if (!response.IsRequestBeingRedirected) return false;
if (string.IsNullOrWhiteSpace(response.RedirectLocation)) return false;
Uri formsAuthUri;
try
{
formsAuthUri = new Uri(FormsAuthentication.LoginUrl);
}
catch (Exception ex)
{
throw new XifCriticalException("Could not construct formsAuthUri", ex);
}
Uri redirctUri;
try
{
redirctUri = new Uri(response.RedirectLocation, UriKind.RelativeOrAbsolute);
}
catch (Exception ex)
{
throw new XifCriticalException("Could not construct redirctUri", ex);
}
try
{
//Check if the request was redirected by Forms Auth
bool isFormsAuthenticationLogin = redirctUri.IsAbsoluteUri &&
redirctUri.Host == formsAuthUri.Host
&& redirctUri.PathAndQuery.Contains(formsAuthUri.PathAndQuery);
return isFormsAuthenticationLogin;
}
catch (Exception ex)
{
throw new XifCriticalException("Could not construct isFormsAuthenticationLogin", ex);
}
}
One other note, to be able to rely on this code in MVC3 you may also need to specify the app settings
<add key="enableSimpleMembership" value="false" />
<add key="autoFormsAuthentication" value="false" />
Couple things.
First, since you're apparently in a controller action, you should replace
Server.Transfer(returnUrl, true);
with
return Redirect(returnUrl);
Second, I generally prefer to not use ajax calls to do authentication. Users should explicitly move from unauthenticated to authenticated states.
Third, if you must do ajax calls where you would normally do redirects, you can do one of two things. In your controller, you can determine it's an ajax request by calling the extension method IsAjaxRequest() (returns true if it is) and branching a different result if it is an ajax request. Second, if you return a Redirect, then it will return to the client a HTTP redirect, which the Ajax client code should be able to read and respond to (e.g. by reading the Location and then doing an ajax fetch on that). Again, I don't recommend this course, but it's possible.
Finally, as a complete left turn... have you considered leaving forms auth alone and instead using a custom MembershipProvider? You can use that to validate membership via Delphi and then set the client cookies using the normal FormsAuth objects like they do in the sample AccountController in MVC.
精彩评论