내 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이 프로세스 뒤에서 업데이트 될 수 있다고 생각합니다.