4

I'm trying to do what should be a simple thing in MVC3.

I've got an application that uses forms authentication to authenticate users with a 3rd party SSO. The SSO, on successful login, posts back to a specific controller action on my application. I then call FormsAuthentication.SetAuthCookie(user,false);.

I'm trying to implement some level of authorization. Simply, a user can exist in a number of different roles, e.g. Admin and Developer. Some controller actions should only be available to certain roles. Details of which roles a user belongs to is obtained by making a call to another external API, which returns a simple JSON response indicating.

In theory, this should be as simple as doing something like this after I set the FormsAuthentication cookie:

string[] rolelist = GetRoleListForUserFromAPI(User.Identity.Name);
HttpContext.User = new GenericPrincipal(User.Identity, rolelist);

However, I can't call this directly after calling SetAuthCookie, because HttpContext.User isn't anything meaningful at this point.

I could try setting this on every request, but ever request to my app would mean a roundtrip API call.

The most promising approach I've seen so far is to create a custom Authorization attribute and override OnAuthorization to do something like this:

public override void OnAuthorization(AuthorizationContext filterContext)
{
    if (<some way of checking if roles have already been set for this user, or role cache has timed out>)
    {
        string[] rolelist = GetRoleListForUserFromAPI(filterContext.HttpContext.User.Identity.Name);
        filterContext.HttpContext.User = new GenericPrincipal(filterContext.HttpContext.User.Identity,rolelist);
    }
}

I could then use [MyCustomAuthorization(Roles="Admin")] in front of controller actions to make the magic happen.

However, I've no idea how to detect whether or not the current HttpContext.User object has had its roles set, or whether it was set over a certain time ago and another API trip is needed.

What's the best approach for this?

4

4 回答 4

2

You should override PostAuthenticateRequest

protected void Application_OnPostAuthenticateRequest(object sender, EventArgs e) 
{
    if (HttpContext.Current.User.Identity.IsAuthenticated)
    {
        string[] rolelist = GetRoleListForUserFromAPI(User.Identity.Name);
        HttpContext.User = new GenericPrincipal(User.Identity, rolelist);
    }
}

It's invoked after forms authentication is finished with it's processing.

http://msdn.microsoft.com/en-us/library/ff647070.aspx

Update

I had the wrong method signature (just checked in one of my own applications).

于 2012-06-08T12:48:25.053 回答
2

Another way would be to store the roles in the UserData property of the FormsAuthentcationTicket. This could be done with comma delimited string.

http://msdn.microsoft.com/en-us/library/system.web.security.formsauthenticationticket.formsauthenticationticket

Then on AuthenticateRequest method, you could pull the ticket back, grab the roles data and assign it to the current user using a generic principal.

于 2012-06-08T12:56:28.730 回答
1

My first thought is that you should investigate implementing a custom role provider. This might be overkill but seems to fit in with the role-based plumbing.

More info from MSDN here.

于 2012-06-08T15:11:55.377 回答
1

Much to the aghast of some, the session object ISNT a bad idea here. If you use temp data, you already take a hit for the session.

Storing this data in the cookie, well - Forms auth tokens have already been exploited in the POET vulnerability from a year and a half ago, so in that case someone could've simply formed their own cookie with the "admin" string in it using that vulnerability.

You can do this in post authenticate as @jgauffin mentioned. If the session state isn't available there you can use it then in Application_PreRequestHandlerExecute and check it there.

If you want to check if session state is available in either see my code at: How can I handle forms authentication timeout exceptions in ASP.NET?

Also whenever using forms auth and sessions, you always want to make sure the timeouts are in sync with each other (again the above code)

于 2012-06-08T18:38:34.053 回答