简体   繁体   中英

User impersonation through HTTP header

I need to implement user impersonation through a HTTP header. For example, a user will send a request to /api/something with the header impersonate=someUser .

I tried to following process:

  1. User gets authenticated by one of multiple authentication schemes.
  2. The authenticated user gets replaced by the impersonated user, if it passes some security checks.
  3. The /api/something endpoint is called

I wrote some custom middleware for this, that runs just after the builtin authentication middelware:

if (!context.Request.Headers.TryGetValue("%Impersonation header%", out StringValues subject))
{
    await _next(context);
    return;
}

if (context.User?.Identity?.IsAuthenticated != true)
{
    // return error
}

...

context.User = impersonatedUser
await _next(context);

However, when it finally reaches the controller, the initial user is still used because the ClaimsPrincipal has been replaced by the default authorization into a new object with two identities. The first identity is the real user, the second identity is the impersonated user.

I could potentially resolve the user then using the second identity, but I'm not sure this process is following best practices?

Edit: this is for ASP.NET Core 2.2 / 3.1

configure IISServerOptions in Startup.cs

services.AddAuthentication(IISDefaults.AuthenticationScheme);
        services.AddSimpleRoleAuthorization<CustomWindowsAuthenticationProvider>();
        services.Configure<IISServerOptions>(opt=>{
            opt.AutomaticAuthentication=true;
            opt.AuthenticationDisplayName="SIMS";
        });

then implement your own IClaimsTransformation to validate the user and set the claims apropriately

public class CustomWindowsAuthenticationProvider : ISimpleRoleProvider
{
    public CustomWindowsAuthenticationProvider(UnitOfWork unitOfWork)
    {
        this._unitOfWork = unitOfWork;
    }
    private UnitOfWork _unitOfWork;

    public Task<ICollection<string>> GetUserRolesAsync(string userName)
    {
        ICollection<string> result = new string[0];
        string[] user = userName.Split("\\");
        var roles = _unitOfWork.UserMod.GetRolesForUser(user[1]);
        if (roles!=null)
            result = roles.Select(d => d.RoleName).ToArray();


        return Task.FromResult(result);
    }
}
public interface ISimpleRoleProvider
{
    #region Public Methods

    /// <summary>
    /// Loads and returns the role names for a given user name.
    /// </summary>
    /// <param name="userName">The login name of the user for which to return the roles.</param>
    /// <returns>
    /// A collection of <see cref="string" /> that describes the roles assigned to the user;
    /// An empty collection of no roles are assigned to the user.
    /// </returns>
    /// <remarks>
    ///     <para>Beware that this method is called for each controller call. It might impact performance.</para>
    ///     <para>
    ///     If Windows authentication is used, the passed <paramref name="userName" />
    ///     is the full user name including the domain or machine name (e.g "CostroDomain\JohnDoe" or
    ///     "JOHN-WORKSTATION\JohnDoe").
    ///     </para>
    ///     <para>
    ///     The returned roles names can be used to restrict access to controllers using the <see cref="AuthorizeAttribute" />
    ///     (<c>[Authorize(Roles="...")]</c>
    ///     </para>
    /// </remarks>
    Task<ICollection<string>> GetUserRolesAsync(string userName);

    #endregion
}
public class SimpleRoleAuthorizationTransform : IClaimsTransformation
{
    #region Private Fields

    private static readonly string RoleClaimType = ClaimTypes.Role;//  $"http://{typeof(SimpleRoleAuthorizationTransform).FullName.Replace('.', '/')}/role";
    private readonly ISimpleRoleProvider _roleProvider;

    #endregion

    #region Public Constructors

    public SimpleRoleAuthorizationTransform(ISimpleRoleProvider roleProvider)
    {
        _roleProvider = roleProvider ?? throw new ArgumentNullException(nameof(roleProvider));
    }

    #endregion

    #region Public Methods

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        // Cast the principal identity to a Claims identity to access claims etc...
        var oldIdentity = (ClaimsIdentity)principal.Identity;

        // "Clone" the old identity to avoid nasty side effects.
        // NB: We take a chance to replace the claim type used to define the roles with our own.
        var newIdentity = new ClaimsIdentity(
            oldIdentity.Claims,
            oldIdentity.AuthenticationType,
            oldIdentity.NameClaimType,
            RoleClaimType);

        // Fetch the roles for the user and add the claims of the correct type so that roles can be recognized.
        var roles = await _roleProvider.GetUserRolesAsync(newIdentity.Name);
        if(roles.Count>0)
            newIdentity.AddClaims(roles.Select(r => new Claim(RoleClaimType, r)));

        // Create and return a new claims principal
        return new ClaimsPrincipal(newIdentity);
    }

    #endregion
}
public static class SimpleRoleAuthorizationServiceCollectionExtensions
{
    #region Public Static Methods

    /// <summary>
    /// Activates simple role authorization for Windows authentication for the ASP.Net Core web site.
    /// </summary>
    /// <typeparam name="TRoleProvider">The <see cref="Type"/> of the <see cref="ISimpleRoleProvider"/> implementation that will provide user roles.</typeparam>
    /// <param name="services">The <see cref="IServiceCollection"/> onto which to register the services.</param>
    public static void AddSimpleRoleAuthorization<TRoleProvider>(this IServiceCollection services)
        where TRoleProvider : class, ISimpleRoleProvider
    {
        services.AddScoped<ISimpleRoleProvider, TRoleProvider>();
        services.AddScoped<IClaimsTransformation, SimpleRoleAuthorizationTransform>();
    }

    #endregion
}

after that you can host the app in iis, and use the iis authentication to determine what kind of method and settings you want to use.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM