Using Custom RoleProvider with Windows Identity Fo

2019-05-10 05:36发布

问题:

I created STS that does the authentication part. It uses Custom Membership provider. After successful login I get redirected to my RP website. All works fine in terms of authentication.

I have defined a CustomRolesProvider defined in web.config of my RP website. It uses the username returned by STS to fetch the roles for that user from RP's database. When I use Roles.GetRolesForUser I do get the right roles.

I have the following in the web.config of my RP to allow only admin to give access to admin folder.

And the sitemap provider has securityTrimmingEnabled="true"

<location path="admin">
    <system.web>
      <authorization>
        <allow roles="admin" />
        <deny users="*" />
      </authorization>
    </system.web>
      </location>

<add name="default" type="System.Web.XmlSiteMapProvider" siteMapFile="Web.sitemap" securityTrimmingEnabled="true" />

Problem: When the user is in the admin role, the menu tabs for admin pages won't showup. I did check that Roles.IsUserInRole("admin") returns true. So the role is recognized by roles provider but not by authorization rules and sitemap provider in the web.config.

If I comment out the "location" from the web.config i.e. allowing every logged-in user to admin folder, my menu items show up fine.

From my understanding of WIF, RP can have it's own implementation of Roles and does not have to rely on Roles Claim from STS.

Does anyone has any ideas?

Update 2(01/20/2012): I found that the STS returns role claims as below:

http://schemas.microsoft.com/ws/2008/06/identity/claims/role = Manager

So if I change <allow roles="admin" /> to <allow roles="Manager" /> the role is picked up and menu tabs are shown appropriately.

So I am sure I am missing a link on how to make use of my roles and not the one returned via claims.

Update 2(01/20/2012): If I add the role to the claimsIdentity like below it works:

void Application_AuthenticateRequest(object sender, EventArgs e) {
  if (Request.IsAuthenticated) {    
    IClaimsPrincipal claimsPrincipal = HttpContext.Current.User as IClaimsPrincipal;
    IClaimsIdentity claimsIdentity = (IClaimsIdentity)claimsPrincipal.Identity;
    if (!claimsIdentity.Claims.Exists(c => c.ClaimType == ClaimTypes.Role))
    {
      claimsIdentity.Claims.Add(new Claim(ClaimTypes.Role, "admin"));
    }
  }
}

But then what would be the best place to add that code? If I add it in Application_AuthenticateRequest it's added upon each request and it keeps adding.(I fixed this by adding if statement)

*Update 3(01/24/2012):*Version 2 of my code that uses my CustomRoleProvider to get the Roles and then add it to the ClaimsCollection:

void Application_AuthenticateRequest(object sender, EventArgs e) {
 if (Request.IsAuthenticated) {
    string[] roleListArray = Roles.GetRolesForUser(User.Identity.Name);
       IClaimsPrincipal claimsPrincipal = HttpContext.Current.User as IClaimsPrincipal;
       IClaimsIdentity claimsIdentity = (IClaimsIdentity)claimsPrincipal.Identity;
       var roleclaims = claimsIdentity.Claims.FindAll(c => c.ClaimType == ClaimTypes.Role);
       foreach (Claim item in roleclaims)
       {
         claimsIdentity.Claims.Remove(item);
       }

       foreach(string role in roleListArray)
       {
         claimsIdentity.Claims.Add(new Claim(ClaimTypes.Role, role));
       }

       HttpContext.Current.User = claimsPrincipal;
    }

But I am not sure if that's the right way.

Is there anyone who has done something like this??

Update 4 (01/26/2012): Found that I can use Custom ClaimsAuthencationManager(Step 4) to transform my claims. I moved the code in AuthenticateRequest method in Global.asax to Authenticate method in ClaimsAuthenticationManager class.

I doubt it can get any better than this. I will post my solution as answer. But still if anyone has any other better solution feel free to comment.

回答1:

You could use a custom ClaimsAuthencationManager, however, it will be called on every request. My recommendation would be to use WSFederationAuthenticationModule.SecurityTokenValidated. Use the ClaimsPrincipal property of SecurityTokenValidatedEventArgs class and add the roles using your provider. Also, instead of hard coding the role claim type, you may wish to consider using ClaimsIdentity.RoleClaimType.

The looked up roles will be saved in the encrypted cookie (assuming you are using the default).



回答2:

The best solution would be to have an IdP (your current STS) and an RP-STS (or Federation Provider). As you say, if in the future you rely on more than one IdP (e.g. you use Live or Google, etc), it is very unlikely that they will provide the claims you need.

The purpose of the RP-STS is precisely to normalize the claimset to whatever your app requires, without polluting your app with identity concerns.

It would look like this:

An RP-STS is especially useful when you have:

  1. Many IdP (yours and external ones)
  2. Many Aplications
  3. Claims transformations that can apply to many RPs. This, the RP-STS being an "authority" on the knowledge of userX being in role Y. And that knowledge not being exclusive of one app.
  4. Protocol transition functions

The transformation (T) would add/remove claims as needed by each app, independently of the IdP.

The reason your app works when you add a "role" claim, but not with Roles.IsUserInRole, is because in general apps check User.IsInRole, which is resolved against the claimset in the principal, and is completed disconnected from Roles provider. This is arguably, a problem in the way Roles provider is designed.

The drawback of an RP-STS is the extra component you need to manage. There are however, rather simpler options today: ACS (Access Control Service) is one. If you are building a custom STS, you could do any of this of course.

The proper place to transform claims in the RP itself is by writing a Custom ClaimsAuthenticationManager (already identitfied by you). At least, that's the "official" extensibility point for doing it. Other soutions might work too though.