내 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′ 클레임을 제거 할 수 없습니다. 이 ID의 일부가 아니거나이 ID를 포함하는 주체가 소유 한 클레임입니다. 예를 들어 역할이있는 GenericPrincipal을 만들 때 Principal이 클레임을 소유합니다. 역할은 생성자에 전달 된 ID를 통해 노출되지만 실제로 ID가 소유하지는 않습니다. RolePrincipal에 대해서도 유사한 논리가 존재합니다.
누군가 청구를 업데이트하는 방법을 알아낼 수 있습니까?
답변
주어진 ClaimsIdentity를 기반으로 클레임을 추가 / 업데이트 / 읽는 확장 메서드를 만들었습니다.
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);
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.GetClaim("key2");
}
}
}
답변
새로 만들 수 있으며 ClaimsIdentity
그런 다음 클레임 업데이트를 수행 할 수 있습니다 .
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 }
);
}
답변
Identity의 UserManager 및 SigninManager를 사용하여 Identity 쿠키의 변경 사항을 반영하고 선택적으로 db 테이블 AspNetUserClaims에서 클레임을 제거하는 또 다른 (비동기) 접근 방식 :
// 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);
}
}
답변
.net core 2.1과 함께 최신 Asp.Net Identity를 사용하여 다음 논리로 사용자 클레임을 업데이트 할 수 있습니다.
-
사용자가 노래
UserClaimsPrincipalFactory
할 때마다SignInManager
클레임이 생성되도록를 등록합니다 .services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimService>();
-
UserClaimsPrincipalFactory<TUser, TRole>
아래와 같은 사용자 지정 구현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; } }
-
나중에 응용 프로그램에서 DB에서 무언가를 변경하고이를 인증되고 로그인 한 사용자에게 반영하고 싶을 때 다음 줄이이를 달성합니다.
var user = await _userManager.GetUserAsync(User); await _signInManager.RefreshSignInAsync(user);
이렇게하면 사용자가 다시 로그인하지 않고도 최신 정보를 볼 수 있습니다. 결과를 컨트롤러에 반환하기 직전에 이것을 넣어 작업이 완료되면 모든 것이 안전하게 새로 고쳐집니다.
기존 클레임을 편집하고 보안 쿠키 등에 대한 경쟁 조건을 만드는 대신 사용자를 자동으로 로그인하고 상태를 새로 고칩니다.
답변
나는 그 예외도 얻고 이렇게 일을 정리했습니다.
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);
답변
여기에서 몇 가지 답변을 내 추가 사항과 함께 재사용 가능한 ClaimsManager 클래스 로 컴파일했습니다 .
클레임이 지속되고 사용자 쿠키가 업데이트되었으며 로그인이 새로 고쳐졌습니다.
이전을 사용자 정의하지 않은 경우 ApplicationUser를 IdentityUser로 대체 할 수 있습니다. 또한 제 경우에는 개발 환경에서 약간 다른 로직이 필요하므로 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>();
}
}
}
답변
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 함수에서 클레임 결과를 확인할 때 어쨌든 역할 값 사용을 가져올 수 없습니다. 그러나…
이 요청이 완료되면 다른 작업 (추가 요청)에서 Role에 액세스 할 수 있습니다.
var userWithClaims = (ClaimsPrincipal)User;
Claim CRole = userWithClaims.Claims.First(c => c.Type == ClaimTypes.Role);
그래서 비동기로 인해 IEnumerable이 프로세스 뒤에서 업데이트 될 수 있다고 생각합니다.