在 Web Api 2 中授予刷新令牌时更新角色

2024-04-03

我在 Asp.Net Web Api 2 中开发了一种身份验证机制,具有授予刷新令牌的功能,基于以下教程泰瑟尔的博客。 http://bitoftech.net/2014/06/09/angularjs-token-authentication-using-asp-net-web-api-2-owin-asp-net-identity/

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

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

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));
        }

这件作品工作得很好(尽管我相信应该有更好的方法来做到这一点?!)

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

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));
            }
        }

再次强调,如果有更好的方法,我将不胜感激您的帮助!但主要的问题是,用于删除不再有效的角色的部分不起作用。 你知道那件作品有什么问题吗?

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

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;
    }


}

这个问题很难回答,而且由于需要包含的内容很多,所以我尝试将一些问题分开。

Claims

有两种方法可以将声明添加到 ClaimsIdentity。

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

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

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

顺便说一句,您可以使用属性扩展 IdentityUser,但也可以将用户声明添加到存储中。在这两种情况下,声明都会添加到 ClaimsIdentity 中。必须在 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;
    }
}

Flow

在发出新的 access_token 之前,服务器必须验证用户。可能有一些原因导致服务器无法颁发新的access_token。还必须考虑更改的配置。有两个提供者可以进行此设置。 access_token 提供程序和refresh_token 提供程序。

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

提供商支持各种流程。你可以在这里读到它:https://msdn.microsoft.com/en-us/library/microsoft.owin.security.oauth.oauthauthorizationserverprovider(v=vs.113).aspx https://msdn.microsoft.com/en-us/library/microsoft.owin.security.oauth.oauthauthorizationserverprovider(v=vs.113).aspx

该提供商是作为选择加入而构建的。如果您不重写某些方法,则访问将被拒绝。

访问令牌

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

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

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 方法中,您可以设置过期时间并选择将刷新令牌添加到票证中。当前令牌尚未过期时,请勿发行新令牌。否则用户可能永远不必再次登录!

如果刷新令牌以某种方式保留下来,您始终可以添加它。或者您可以仅在登录时添加新的刷新令牌。用户已被识别,因此“旧”refresh_token 不再重要,因为它会在新的refresh_token 之前过期。如果您只想使用一个活动的 refesh_token,那么您必须保留它。

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);
    }
}

这只是刷新令牌流程的简单实现,尚未完成或经过测试。这只是为了给您一些关于实现刷新令牌流程的想法。正如您所看到的,向 ClaimsIdentity 添加声明并不难。我没有添加维护持久声明的代码。重要的是坚持不懈的主张是自动地 added!

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Web Api 2 中授予刷新令牌时更新角色 的相关文章

随机推荐