如何更新 ASP.NET 标识中的声明?

我在我的 MVC5项目中使用 OWIN 身份验证。 这是我的 SignInAsync

 private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
var AccountNo = "101";
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim(ClaimTypes.UserData, AccountNo));
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent, RedirectUri="Account/Index"}, identity);
}

如您所见,我将 AccountNo添加到索赔列表中。

现在,我如何在应用程序的某个时刻更新这个声明:

 public string AccountNo
{


get
{
var CP = ClaimsPrincipal.Current.Identities.First();
var Account= CP.Claims.FirstOrDefault(p => p.Type == ClaimTypes.UserData);
return Account.Value;
}
set
{
var CP = ClaimsPrincipal.Current.Identities.First();
var AccountNo= CP.Claims.FirstOrDefault(p => p.Type == ClaimTypes.UserData).Value;
CP.RemoveClaim(new Claim(ClaimTypes.UserData,AccountNo));
CP.AddClaim(new Claim(ClaimTypes.UserData, value));
}


}

当我试图删除索赔,我得到这个例外:

索赔 'http://schemas.microsoft.com/ws/2008/06/identity/claims/userdata: 101’无法被删除。它要么不是这个的一部分 标识或由主体拥有的声明,其中包含 例如,在下列情况下,委托人将拥有该项申索: 创建一个包含角色的 GenericPrime。这些角色将被公开 通过构造函数中传递的标识,但不包含 类似的逻辑存在于 角色校长。

如何删除和更新索赔?

141381 次浏览

You can create a new ClaimsIdentity and then do the claims update with such.

set {
// get context of the authentication manager
var authenticationManager = HttpContext.GetOwinContext().Authentication;


// create a new identity from the old one
var identity = new ClaimsIdentity(User.Identity);


// update claim value
identity.RemoveClaim(identity.FindFirst("AccountNo"));
identity.AddClaim(new Claim("AccountNo", value));


// tell the authentication manager to use this new identity
authenticationManager.AuthenticationResponseGrant =
new AuthenticationResponseGrant(
new ClaimsPrincipal(identity),
new AuthenticationProperties { IsPersistent = true }
);
}

我也得到了这个例外,并澄清了这样的事情

var identity = User.Identity as ClaimsIdentity;
var newIdentity = new ClaimsIdentity(identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType);
newIdentity.AddClaims(identity.Claims.Where(c => false == (c.Type == claim.Type && c.Value == claim.Value)));
// the claim has been removed, you can add it with a new value now if desired
AuthenticationManager.SignOut(identity.AuthenticationType);
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, newIdentity);

我创建了一个扩展方法,可以基于给定的声明身份添加/更新/读取声明

namespace Foobar.Common.Extensions
{
public static class Extensions
{
public static void AddUpdateClaim(this IPrincipal currentPrincipal, string key, string value)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return;


// check for existing claim and remove it
var existingClaim = identity.FindFirst(key);
if (existingClaim != null)
identity.RemoveClaim(existingClaim);


// add new claim
identity.AddClaim(new Claim(key, value));
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true });
}


public static string GetClaimValue(this IPrincipal currentPrincipal, string key)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return null;


var claim = identity.Claims.FirstOrDefault(c => c.Type == key);


// ?. prevents a exception if claim is null.
return claim?.Value;
}
}
}

然后使用它

using Foobar.Common.Extensions;


namespace Foobar.Web.Main.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
// add/updating claims
User.AddUpdateClaim("key1", "value1");
User.AddUpdateClaim("key2", "value2");
User.AddUpdateClaim("key3", "value3");
}


public ActionResult Details()
{
// reading a claim
var key2 = User.GetClaimValue("key2");
}
}
}

The extension method worked great for me with one exception that if the user logs out there old claim sets still existed so with a tiny modification as in passing usermanager through everything works great and you dont need to logout and login. 我不能直接回答这个问题,因为我的名声已经被诋毁了:

public static class ClaimExtensions
{
public static void AddUpdateClaim(this IPrincipal currentPrincipal,    string key, string value, ApplicationUserManager userManager)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return;


// check for existing claim and remove it
var existingClaim = identity.FindFirst(key);
if (existingClaim != null)
{
RemoveClaim(currentPrincipal, key, userManager);
}


// add new claim
var claim = new Claim(key, value);
identity.AddClaim(claim);
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant(new ClaimsPrincipal(identity), new AuthenticationProperties() { IsPersistent = true });
//Persist to store
userManager.AddClaim(identity.GetUserId(),claim);


}


public static void RemoveClaim(this IPrincipal currentPrincipal, string key, ApplicationUserManager userManager)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return ;


// check for existing claim and remove it
var existingClaims = identity.FindAll(key);
existingClaims.ForEach(c=> identity.RemoveClaim(c));


//remove old claims from store
var user = userManager.FindById(identity.GetUserId());
var claims =  userManager.GetClaims(user.Id);
claims.Where(x => x.Type == key).ToList().ForEach(c => userManager.RemoveClaim(user.Id, c));


}


public static string GetClaimValue(this IPrincipal currentPrincipal, string key)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return null;


var claim = identity.Claims.First(c => c.Type == key);
return claim.Value;
}


public static string GetAllClaims(this IPrincipal currentPrincipal, ApplicationUserManager userManager)
{
var identity = currentPrincipal.Identity as ClaimsIdentity;
if (identity == null)
return null;


var claims = userManager.GetClaims(identity.GetUserId());
var userClaims = new StringBuilder();
claims.ForEach(c => userClaims.AppendLine($"<li>{c.Type}, {c.Value}</li>"));
return userClaims.ToString();
}




}

另一种(异步)方法,使用 Identity 的 UserManager 和 SigninManager 来反映 Identity cookie 中的更改(并可选地从 db 表 AspNetUserClamings 中删除声明) :

// Get User and a claims-based identity
ApplicationUser user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
var Identity = new ClaimsIdentity(User.Identity);


// Remove existing claim and replace with a new value
await UserManager.RemoveClaimAsync(user.Id, Identity.FindFirst("AccountNo"));
await UserManager.AddClaimAsync(user.Id, new Claim("AccountNo", value));


// Re-Signin User to reflect the change in the Identity cookie
await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false);


// [optional] remove claims from claims table dbo.AspNetUserClaims, if not needed
var userClaims = UserManager.GetClaims(user.Id);
if (userClaims.Any())
{
foreach (var item in userClaims)
{
UserManager.RemoveClaim(user.Id, item);
}
}

给你:

            var user = User as ClaimsPrincipal;
var identity = user.Identity as ClaimsIdentity;
var claim = (from c in user.Claims
where c.Type == ClaimTypes.UserData
select c).Single();
identity.RemoveClaim(claim);

取自 给你。

当我使用 MVC5时,在这里添加索赔。

public async Task<ClaimsIdentity> GenerateUserIdentityAsync(PATAUserManager manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
userIdentity.AddClaim(new Claim(ClaimTypes.Role, this.Role));


return userIdentity;
}

当我在 SignInAsync 函数中检查索赔结果时,我无论如何也得不到角色值的使用。

在这个请求完成后,我可以访问其他操作(花药请求)中的角色。

 var userWithClaims = (ClaimsPrincipal)User;
Claim CRole = userWithClaims.Claims.First(c => c.Type == ClaimTypes.Role);

因此,我认为可能异步导致 IEnumable 在进程后面更新。

要从数据库中删除索赔细节,我们可以使用以下代码。此外,我们需要再次登录以更新 cookie 值

 // create a new identity
var identity = new ClaimsIdentity(User.Identity);


// Remove the existing claim value of current user from database
if(identity.FindFirst("NameOfUser")!=null)
await UserManager.RemoveClaimAsync(applicationUser.Id, identity.FindFirst("NameOfUser"));


// Update customized claim
await UserManager.AddClaimAsync(applicationUser.Id, new Claim("NameOfUser", applicationUser.Name));


// the claim has been updates, We need to change the cookie value for getting the updated claim
AuthenticationManager.SignOut(identity.AuthenticationType);
await SignInManager.SignInAsync(Userprofile, isPersistent: false, rememberBrowser: false);


return RedirectToAction("Index", "Home");

多个饼干,多个索赔

public class ClaimsCookie
{
private readonly ClaimsPrincipal _user;
private readonly HttpContext _httpContext;
public ClaimsCookie(ClaimsPrincipal user, HttpContext httpContext = null)
{
_user = user;
_httpContext = httpContext;
}


public string GetValue(CookieName cookieName, KeyName keyName)
{
var principal = _user as ClaimsPrincipal;
var cp = principal.Identities.First(i => i.AuthenticationType == ((CookieName)cookieName).ToString());
return cp.FindFirst(((KeyName)keyName).ToString()).Value;
}
public async void SetValue(CookieName cookieName, KeyName[] keyName, string[] value)
{
if (keyName.Length != value.Length)
{
return;
}
var principal = _user as ClaimsPrincipal;
var cp = principal.Identities.First(i => i.AuthenticationType == ((CookieName)cookieName).ToString());
for (int i = 0; i < keyName.Length; i++)
{
if (cp.FindFirst(((KeyName)keyName[i]).ToString()) != null)
{
cp.RemoveClaim(cp.FindFirst(((KeyName)keyName[i]).ToString()));
cp.AddClaim(new Claim(((KeyName)keyName[i]).ToString(), value[i]));
}


}
await _httpContext.SignOutAsync(CookieName.UserProfilCookie.ToString());
await _httpContext.SignInAsync(CookieName.UserProfilCookie.ToString(), new ClaimsPrincipal(cp),
new AuthenticationProperties
{
IsPersistent = bool.Parse(cp.FindFirst(KeyName.IsPersistent.ToString()).Value),
AllowRefresh = true
});
}
public enum CookieName
{
CompanyUserProfilCookie = 0, UserProfilCookie = 1, AdminPanelCookie = 2
}
public enum KeyName
{
Id, Name, Surname, Image, IsPersistent
}
}
    if (HttpContext.User.Identity is ClaimsIdentity identity)
{
identity.RemoveClaim(identity.FindFirst("userId"));
identity.AddClaim(new Claim("userId", userInfo?.id.ToString()));
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(HttpContext.User.Identity));
}

使用最新的 Asp.Net Identity 和.Net core 2.1,我能够使用以下逻辑更新用户声明。

  1. 注册一个 UserClaimsPrincipalFactory,以便每次 SignInManager唱用户在,索赔创建。

    services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimService>();
    
  2. Implement a custom UserClaimsPrincipalFactory<TUser, TRole> like below

    public class UserClaimService : UserClaimsPrincipalFactory<ApplicationUser, ApplicationRole>
    {
    private readonly ApplicationDbContext _dbContext;
    
    
    public UserClaimService(ApplicationDbContext dbContext, UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
    {
    _dbContext = dbContext;
    }
    
    
    public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
    {
    var principal = await base.CreateAsync(user);
    
    
    // Get user claims from DB using dbContext
    
    
    // Add claims
    ((ClaimsIdentity)principal.Identity).AddClaim(new Claim("claimType", "some important claim value"));
    
    
    return principal;
    }
    }
    
  3. Later in your application when you change something in the DB and would like to reflect this to your authenticated and signed in user, following lines achieves this:

    var user = await _userManager.GetUserAsync(User);
    await _signInManager.RefreshSignInAsync(user);
    

This makes sure user can see up to date information without requiring login again. I put this just before returning the result in the controller so that when the operation finishes, everything securely refreshed.

Instead of editing existing claims and creating race conditions for secure cookie etc, you just sign user in silently and refresh the state :)

用我的加法把一些答案从这里汇编到 可重用的声明管理器课上。

声明得到持久化,用户 cookie 得到更新,登录得到更新。

请注意,如果没有自定义 ApplicationUser,可以用 IdentityUser 替换 ApplicationUser。在我的例子中,它在 Development 环境中需要稍微有些不同的逻辑,因此您可能希望删除 IWebHostEnvironment 依赖项。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using YourMvcCoreProject.Models;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Hosting;


namespace YourMvcCoreProject.Identity
{
public class ClaimsManager
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IWebHostEnvironment _env;
private readonly ClaimsPrincipalAccessor _currentPrincipalAccessor;


public ClaimsManager(
ClaimsPrincipalAccessor currentPrincipalAccessor,
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IWebHostEnvironment env)
{
_currentPrincipalAccessor = currentPrincipalAccessor;
_userManager = userManager;
_signInManager = signInManager;
_env = env;
}


/// <param name="refreshSignin">Sometimes (e.g. when adding multiple claims at once) it is desirable to refresh cookie only once, for the last one </param>
public async Task AddUpdateClaim(string claimType, string claimValue, bool refreshSignin = true)
{
await AddClaim(
_currentPrincipalAccessor.ClaimsPrincipal,
claimType,
claimValue,
async user =>
{
await RemoveClaim(_currentPrincipalAccessor.ClaimsPrincipal, user, claimType);
},
refreshSignin);
}


public async Task AddClaim(string claimType, string claimValue, bool refreshSignin = true)
{
await AddClaim(_currentPrincipalAccessor.ClaimsPrincipal, claimType, claimValue, refreshSignin);
}


/// <summary>
/// At certain stages of user auth there is no user yet in context but there is one to work with in client code (e.g. calling from ClaimsTransformer)
/// that's why we have principal as param
/// </summary>
public async Task AddClaim(ClaimsPrincipal principal, string claimType, string claimValue, bool refreshSignin = true)
{
await AddClaim(
principal,
claimType,
claimValue,
async user =>
{
// allow reassignment in dev
if (_env.IsDevelopment())
await RemoveClaim(principal, user, claimType);


if (GetClaim(principal, claimType) != null)
throw new ClaimCantBeReassignedException(claimType);
},
refreshSignin);
}


public async Task RemoveClaims(IEnumerable<string> claimTypes, bool refreshSignin = true)
{
await RemoveClaims(_currentPrincipalAccessor.ClaimsPrincipal, claimTypes, refreshSignin);
}


public async Task RemoveClaims(ClaimsPrincipal principal, IEnumerable<string> claimTypes, bool refreshSignin = true)
{
AssertAuthenticated(principal);
foreach (var claimType in claimTypes)
{
await RemoveClaim(principal, claimType);
}
// reflect the change in the Identity cookie
if (refreshSignin)
await _signInManager.RefreshSignInAsync(await _userManager.GetUserAsync(principal));
}


public async Task RemoveClaim(string claimType, bool refreshSignin = true)
{
await RemoveClaim(_currentPrincipalAccessor.ClaimsPrincipal, claimType, refreshSignin);
}


public async Task RemoveClaim(ClaimsPrincipal principal, string claimType, bool refreshSignin = true)
{
AssertAuthenticated(principal);
var user = await _userManager.GetUserAsync(principal);
await RemoveClaim(principal, user, claimType);
// reflect the change in the Identity cookie
if (refreshSignin)
await _signInManager.RefreshSignInAsync(user);
}


private async Task AddClaim(ClaimsPrincipal principal, string claimType, string claimValue, Func<ApplicationUser, Task> processExistingClaims, bool refreshSignin)
{
AssertAuthenticated(principal);
var user = await _userManager.GetUserAsync(principal);
await processExistingClaims(user);
var claim = new Claim(claimType, claimValue);
ClaimsIdentity(principal).AddClaim(claim);
await _userManager.AddClaimAsync(user, claim);
// reflect the change in the Identity cookie
if (refreshSignin)
await _signInManager.RefreshSignInAsync(user);
}


/// <summary>
/// Due to bugs or as result of debug it can be more than one identity of the same type.
/// The method removes all the claims of a given type.
/// </summary>
private async Task RemoveClaim(ClaimsPrincipal principal, ApplicationUser user, string claimType)
{
AssertAuthenticated(principal);
var identity = ClaimsIdentity(principal);
var claims = identity.FindAll(claimType).ToArray();
if (claims.Length > 0)
{
await _userManager.RemoveClaimsAsync(user, claims);
foreach (var c in claims)
{
identity.RemoveClaim(c);
}
}
}


private static Claim GetClaim(ClaimsPrincipal principal, string claimType)
{
return ClaimsIdentity(principal).FindFirst(claimType);
}


/// <summary>
/// This kind of bugs has to be found during testing phase
/// </summary>
private static void AssertAuthenticated(ClaimsPrincipal principal)
{
if (!principal.Identity.IsAuthenticated)
throw new InvalidOperationException("User should be authenticated in order to update claims");
}


private static ClaimsIdentity ClaimsIdentity(ClaimsPrincipal principal)
{
return (ClaimsIdentity) principal.Identity;
}
}




public class ClaimCantBeReassignedException : Exception
{
public ClaimCantBeReassignedException(string claimType) : base($"{claimType} can not be reassigned")
{
}
}


public class ClaimsPrincipalAccessor
{
private readonly IHttpContextAccessor _httpContextAccessor;


public ClaimsPrincipalAccessor(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}


public ClaimsPrincipal ClaimsPrincipal => _httpContextAccessor.HttpContext.User;
}


// to register dependency put this into your Startup.cs and inject ClaimsManager into Controller constructor (or other class) the in same way as you do for other dependencies
public class Startup
{
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddTransient<ClaimsPrincipalAccessor>();
services.AddTransient<ClaimsManager>();
}
}

}

可以通过实现 CookieAuthenticationEvents类并重写 ValidatePrincipal来更新当前用户的声明。在那里,您可以删除旧的声明,添加新的声明,然后使用 CookieValidatePrincipalContext.ReplacePrincipal替换主体。这不会影响存储在数据库中的任何声明。这是使用 ASP.NET 核心标识2.2。

public class MyCookieAuthenticationEvents : CookieAuthenticationEvents
{
string newAccountNo = "102";


public override Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
// first remove the old claim
var claim = context.Principal.FindFirst(ClaimTypes.UserData);
if (claim != null)
{
((ClaimsIdentity)context.Principal.Identity).RemoveClaim(claim);
}


// add the new claim
((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim(ClaimTypes.UserData, newAccountNo));


// replace the claims
context.ReplacePrincipal(context.Principal);
context.ShouldRenew = true;


return Task.CompletedTask;
}
}

您需要在 Startup.cs中注册事件类:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddScoped<MyCookieAuthenticationEvents>();


services.ConfigureApplicationCookie(o =>
{
o.EventsType = typeof(MyCookieAuthenticationEvents);
});
}

You can inject services into the events class to access the new AccountNo value but as per the warning on 这一页 you should avoid doing anything too expensive:

警告

这里描述的方法在每个请求上都会被触发 对于每个请求的所有用户,身份验证 Cookie 可能导致 对应用程序的巨大性能损失。

我使用的是.net core 2.2应用程序,并使用了以下解决方案: 在我的雕像里

public void ConfigureServices(IServiceCollection services)
{
...
services.AddIdentity<IdentityUser, IdentityRole>(options =>
{
...
})
.AddEntityFrameworkStores<AdminDbContext>()
.AddDefaultTokenProviders()
.AddSignInManager();


用途

  private readonly SignInManager<IdentityUser> _signInManager;




public YourController(
...,
SignInManager<IdentityUser> signInManager)
{
...
_signInManager = signInManager;
}


public async Task<IActionResult> YourMethod() // <-NOTE IT IS ASYNC
{
var user = _userManager.FindByNameAsync(User.Identity.Name).Result;
var claimToUse = ClaimsHelpers.CreateClaim(ClaimTypes.ActiveCompany, JsonConvert.SerializeObject(cc));
var claimToRemove = _userManager.GetClaimsAsync(user).Result
.FirstOrDefault(x => x.Type == ClaimTypes.ActiveCompany.ToString());
if (claimToRemove != null)
{
var result = _userManager.ReplaceClaimAsync(user, claimToRemove, claimToUse).Result;
await _signInManager.RefreshSignInAsync(user); //<--- THIS
}
else ...
              

目前为我更新现有索赔的最简单的解决办法是:

//updating user data
await signInManager.SignOutAsync();
await signInManager.SignInAsync(user, false);

谢谢你问我这个问题。NET 4/OWIN,但是为了帮助搜索者寻找。NET 5或更高版本的等价物,这里有一些示例代码。

我相信你可以改善它,但它是一个工作启动使用 UserManagerSignInManagerMicrosoft.AspNetCore.Identity

// Get the user first first.
var claims = await _userManager.GetClaimsAsync(user);
var givenNameClaim = claims.FirstOrDefault(r => r.Type == JwtClaimTypes.GivenName);


IdentityResult result = null;


if (givenNameClaim != null)
{
result = await _userManager.ReplaceClaimAsync(user, givenNameClaim, new Claim(JwtClaimTypes.GivenName, "<newvalue>"));
}
else
{
result = await _userManager.AddClaimAsync(user, new Claim(JwtClaimTypes.GivenName, "<newvalue>"));
}


if (result.Errors.Any())
{
// TODO: List errors here;
}
else
{
await _signInManager.RefreshSignInAsync(user); // refresh the login, so it takes effect immediately.
}

我知道这是老生常谈了,但是要求好像变了。

下面的内容对我很有用:

            var user = await _userManager.FindByEmailAsync(input.Email);


var userClaim = await _userManager.GetClaimsAsync(user);


var userNameClaims = userClaim.Where(x => x.Type == ClaimTypes.GivenName).ToList();


await _userManager.RemoveClaimsAsync(user, userNameClaims);


await _userManager.AddClaimAsync(user, new Claim(ClaimTypes.GivenName, user.Forename));


await _signInManager.SignOutAsync();
await _signInManager.SignInAsync(user, new AuthenticationProperties() { IsPersistent = input.RememberMe });

签出和在方法是必不可少的,没有索赔不能反映变化