本文介绍了在 Web Api 2 中授予刷新令牌时更新角色的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我根据 Taiseer 的博客.

这是我的问题.假设以下场景:用户使用密码登录并获得刷新令牌和访问令牌.访问令牌实际上包括他的角色(因此他在应用程序中的权限).同时系统管理员会改变这个人的角色,所以一旦他的访问令牌过期并且他想使用刷新令牌获取新的访问令牌,他的新访问令牌必须包括他新更新的角色.

Here is my question. Assume the following scenario:A user logs in using password and get a refresh token and an access token. The access token in fact includes what roles he is in (hence his authorities within the app). In the mean time the system admin will change this person's roles, so once his access token expires and he wants to use the refresh token to obtain a new access token, his new access token must include the newly updated roles for him.

在我的RefreshTokenProvider"类中,我在GrantResourceOwnerCredentials"方法中使用以下代码从数据库中获取用户角色并将它们添加到声明中:

In my "RefreshTokenProvider" class, I am using the following code in "GrantResourceOwnerCredentials" method to get the user roles from the database and add them to the claims:

var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
        var y = roleManager.Roles.ToList();

        var id = new ClaimsIdentity(context.Options.AuthenticationType);
        id.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
        id.AddClaim(new Claim("sub", context.UserName));

        var roles2 = UserRoleManagerProvider.RoleManager().Roles.ToList();

        foreach (IdentityRole i in roles2)
        {
            if (roleIds.Contains(i.Id))
                id.AddClaim(new Claim(ClaimTypes.Role, i.Name));
        }

这件作品很好用(尽管我认为应该有更好的方法来做到这一点?!)

This piece works fine (even though I believe there should be a nicer way to do it?!)

但是不能正常工作的部分是在GrantRefreshToken"方法中,我们需要更新角色以在新的访问令牌中反映它们:

But the part that is not working properly is in the "GrantRefreshToken" method, where we need to update roles in order to reflect them in the new access token:

var newId = new ClaimsIdentity(context.Ticket.Identity);

        // *** Add shit here....

        var userId = context.Ticket.Properties.Dictionary["userId"];
        IdentityUser user = UserRoleManagerProvider.UserManager().FindById(userId);

        foreach (Claim c in newId.Claims)
        {
            if (c.Type == ClaimTypes.Role) newId.RemoveClaim(c);
        }

        if (user.Roles.Count > 0)
        {
            var roleIds = new List<string>();
            var roles2 = UserRoleManagerProvider.RoleManager().Roles.ToList();
            foreach (IdentityUserRole ir in user.Roles)
            {
                roleIds.Add(ir.RoleId);
            }
            foreach (IdentityRole r in roles2)
            {
                if (roleIds.Contains(r.Id))
                    newId.AddClaim(new Claim(ClaimTypes.Role, r.Name));
            }
        }

再说一次,如果有更好的方法来做到这一点,我会很感激你们的帮助!但主要是,我的问题是删除不再有效的角色的部分不起作用.你有没有机会知道那件作品有什么问题?!

Again, if there is a nicer way to do it I'd appreciate you guy's help! But mainly, my problem is that the part for removing the Roles that are not in effect anymore, does not work.Do you by any chance know what is wrong with that piece?!

仅供参考,在上面的代码中,UserRoleManagerProvider"是我创建的一个简单的静态类,如下所示:

FYI, in the above code the "UserRoleManagerProvider" is a simple static class I have created which is like this:

public static class UserRoleManagerProvider
{
    public static RoleManager<IdentityRole> RoleManager()
    {
        var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
        return roleManager;
    }

    public static UserManager<IdentityUser> UserManager()
    {
        var userManager = new UserManager<IdentityUser>(new UserStore<IdentityUser>(new ApplicationDbContext()));
        return userManager;
    }


}

推荐答案

这个问题很难回答,因为需要包含的内容很多,我试着分开一些问题.

It is difficult to answer this question, and since there is a lot that needs to be included, I've tried to seperate some issues.

索赔

有两种方法可以向 ClaimsIdentity 添加声明.

There are two ways to add claims to the ClaimsIdentity.

  1. 在存储中保留声明(在数据库中的表 AspNetUserClaims、AspNetRoleClaims).要添加声明,请使用 UserManager.AddClaim 或 RoleManager.AddClaim.角色 (AspNetUserRoles) 很特殊,因为它们也算作声明.
  2. 在代码中添加声明.您可以从 ApplicationUser 类(对于 IdentityUser 的扩展属性很有用)或在流中添加声明.

请注意区别!虽然在所有情况下都称为 AddClaim,但第一个变体将声明添加到商店,而第二个变体将声明直接添加到 ClaimsIdentity.

Please note the difference! While in all cases it is called AddClaim, the first variant adds the claims to the store, while the second variant adds the claims directly to the ClaimsIdentity.

那么持久声明是如何添加到 ClaimsIdentity 的呢?这是自动完成的!

So how are persisted claims added to the ClaimsIdentity? This is done automatically!

附带说明,您可以使用属性扩展 IdentityUser,但您也可以将用户声明添加到商店.在这两种情况下,声明都将添加到 ClaimsIdentity.必须在 ApplicationUser.GenerateUserIdentityAsync 中添加扩展属性:

As a side note, you can extend the IdentityUser with properties, but you can also add user claims to the store. In both cases the claim will be added to the ClaimsIdentity. The extended property has to be added in ApplicationUser.GenerateUserIdentityAsync:

public class ApplicationUser : IdentityUser
{
    public string DisplayName { get; set; }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);

        // Add custom user claims here
        userIdentity.AddClaim(new Claim("DisplayName", DisplayName));

        return userIdentity;
    }
}

流程

在发布新的 access_token 之前,服务器必须验证用户.服务器无法发出新的 access_token 可能是有原因的.还必须考虑更改的配置.此设置有两个提供程序.access_token 提供程序和 refresh_token 提供程序.

Before issuing a new access_token the server must validate the user. There may be reasons why the server cannot issue a new access_token. Also the changed configuration has to be taken into account. There are two providers for this setup. The access_token provider and the refresh_token provider.

当客户端向令牌端点 (grant_type = *) 发出请求时,首先执行 AccessTokenProvider.ValidateClientAuthentication.如果您使用的是 client_credentials,那么您可以在这里做一些事情.但是对于当前流程,我们假设 context.Validated();

When a clients makes a request to the token endpoint (grant_type = *), AccessTokenProvider.ValidateClientAuthentication is executed first. If you are using client_credentials then you can do something here. But for the current flow we assume context.Validated();

提供者支持各种流.您可以在此处阅读:https://msdn.microsoft.com/en-us/library/microsoft.owin.security.oauth.oauthauthorizationserverprovider(v=vs.113).aspx

The provider supports various flows. You can read about it here: https://msdn.microsoft.com/en-us/library/microsoft.owin.security.oauth.oauthauthorizationserverprovider(v=vs.113).aspx

提供程序内置为选择加入.如果您不覆盖某些方法,则访问将被拒绝.

The provider is built as opt-in. If you do not override the certain methods, then access is denied.

访问令牌

要获得访问令牌,必须发送凭据.对于这个例子,我将假设grant_type = password".在 AccessTokenProvider.GrantResourceOwnerCredentials 中,检查凭据、设置 ClaimsIdentity 并颁发令牌.

To obtain an access token, credentials have to be sent. For this example I will assume 'grant_type = password'. In AccessTokenProvider.GrantResourceOwnerCredentials the credentials are checked, the ClaimsIdentity is setup and a token is issued.

为了将 refresh_token 添加到票证,我们需要覆盖 AccessTokenProvider.GrantRefreshToken.在这里您有两个选择:拒绝令牌.因为 refresh_token 已被撤销,或者由于其他原因不允许用户再使用刷新令牌.或者设置一个新的 ClaimsIdentity 来为票证生成一个新的 access_token.

In order to add a refresh_token to the ticket we need to override AccessTokenProvider.GrantRefreshToken. Here you have two options: reject the token. Because the refresh_token was revoked or for another reason why the user isn't allowed to use the refresh token anymore. Or setup a new ClaimsIdentity to genereate a new access_token for the ticket.

class AccessTokenProvider : OAuthAuthorizationServerProvider
{
    public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
    {
        // Reject token: context.Rejected(); Or:

        // chance to change authentication ticket for refresh token requests
        var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
        var appUser = await userManager.FindByNameAsync(context.Ticket.Identity.Name);
        var oAuthIdentity = await appUser.GenerateUserIdentityAsync(userManager);
        var newTicket = new AuthenticationTicket(oAuthIdentity, context.Ticket.Properties);

        context.Validated(newTicket);
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
        var appUser = await userManager.FindAsync(context.UserName, context.Password);
        if (appUser == null)
        {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }

        var propertyDictionary = new Dictionary<string, string> { { "userName", appUser.UserName } };
        var properties = new AuthenticationProperties(propertyDictionary);

        var oAuthIdentity = await appUser.GenerateUserIdentityAsync(userManager);
        var ticket = new AuthenticationTicket(oAuthIdentity, properties);

        // Token is validated.
        context.Validated(ticket);
    }
}

如果上下文具有经过验证的票证,则调用 RefreshTokenProvider.在 Create 方法中,您可以设置到期时间并选择将刷新令牌添加到票证.不要在当前令牌尚未过期时发行新令牌.否则用户可能永远不必再次登录!

If the context has a validated ticket, the RefreshTokenProvider is called. In the Create method you can set the expiration time and choose to add the refresh token to the ticket. Do not issue new tokens while the current one isn't expired yet. Otherwise the user may never have to login again!

如果它以某种方式持久化,您可以随时添加 refresh_token.或者您可以仅在登录时添加新的 refresh_token.用户已被识别,因此旧"refresh_token 不再重要,因为它将在新 refresh_token 之前到期.如果你只想使用一个活跃的refesh_token,那么你必须坚持它.

You can always add the refresh_token if it is somehow persisted. Or you can add a new refresh_token on login only. The user is identified so the 'old' refresh_token doesn't matter anymore since it will expire before the new refresh_token does. If you want to use one active refesh_token only, then you'll have to persist it.

class RefreshTokenProvider : AuthenticationTokenProvider
{
    public override void Create(AuthenticationTokenCreateContext context)
    {
        var form = context.Request.ReadFormAsync().Result;
        var grantType = form.GetValues("grant_type");

        // do not issue a new refresh_token if a refresh_token was used.
        if (grantType[0] != "refresh_token")
        {
            // 35 days.
            int expire = 35 * 24 * 60 * 60;
            context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddSeconds(expire));
            // Add the refresh_token to the ticket.
            context.SetToken(context.SerializeTicket());
        }
        base.Create(context);
    }

    public override void Receive(AuthenticationTokenReceiveContext context)
    {
        context.DeserializeTicket(context.Token);
        base.Receive(context);
    }
}

这只是 refresh_token 流程的一个简单实现,未完成也未经过测试.只是给你一些实现refresh_token流程的想法.如您所见,向 ClaimsIdentity 添加声明并不难.我没有添加维护持久声明的代码.重要的是,持久化的声明是自动添加的!

This is just a simple implementation of the refresh_token flow and not complete nor tested. It is just to give you some ideas on implementing the refresh_token flow. As you can see it isn't hard to add claims to the ClaimsIdentity. I didn't add code where persisted claims are maintained. All what matters is that the persisted claims are automatically added!

请注意,我在使用 refresh_token 刷新 access_token 时重置了 ClaimsIdentity(新票证).这将创建一个具有当前声明状态的新 ClaimsIdentity.

Please notice that I reset the ClaimsIdentity (new ticket) on refreshing the access_token using the refresh_token. This will create a new ClaimsIdentity with the current state of claims.

我将以最后一句话结束.我说的是角色是声明.您可能希望 User.IsInRole 检查 AspNetUserRoles 表.但事实并非如此.由于角色是声明,它会检查声明集合中的可用角色.

I will end with one final remark. I was talking about roles being claims. You may expect that User.IsInRole checks the AspNetUserRoles table. But it doesn't. As roles are claims it checks the claims collection for available roles.

这篇关于在 Web Api 2 中授予刷新令牌时更新角色的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-22 22:20