c# - Cómo obtener el usuario actual en asp.net core
asp.net-core asp.net-identity (12)
Quiero obtener un usuario actual para obtener información de un usuario, como un correo electrónico. Pero no puedo hacer eso en asp.net core. Estoy tan confundido. Este es mi código.
HttpContext
casi es nulo en el
constructor
del controlador.
No es bueno tener un usuario en cada acción.
Quiero obtener información del usuario una vez y configurarla en
ViewData
;
public DashboardController()
{
var user = HttpContext.User.GetUserId();
}
Además de las respuestas existentes, me gustaría agregar que también puede tener una instancia de clase disponible en toda la aplicación que contiene datos relacionados con el usuario como
UserID
usuario, etc.
Puede ser útil para
refactorizar,
por ejemplo, no desea obtener el
UserID
de
UserID
en cada acción del controlador y declarar un parámetro de
UserID
adicional en cada método relacionado con la capa de servicio.
He hecho una investigación y aquí está mi publicación .
Simplemente extienda su clase que deriva de
DbContext
agregando la propiedad
UserId
(o implemente una clase de
Session
personalizada que tenga esta propiedad).
A nivel de filtro, puede buscar su instancia de clase y establecer el valor de
UserId
.
Después de eso, donde sea que inyecte su instancia, tendrá los datos necesarios (la vida útil debe ser
por solicitud
, por lo que debe registrarla utilizando el método
AddScoped
).
Ejemplo de trabajo:
public class AppInitializationFilter : IAsyncActionFilter
{
private DBContextWithUserAuditing _dbContext;
public AppInitializationFilter(
DBContextWithUserAuditing dbContext
)
{
_dbContext = dbContext;
}
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next
)
{
string userId = null;
int? tenantId = null;
var claimsIdentity = (ClaimsIdentity)context.HttpContext.User.Identity;
var userIdClaim = claimsIdentity.Claims.SingleOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
if (userIdClaim != null)
{
userId = userIdClaim.Value;
}
var tenantIdClaim = claimsIdentity.Claims.SingleOrDefault(c => c.Type == CustomClaims.TenantId);
if (tenantIdClaim != null)
{
tenantId = !string.IsNullOrEmpty(tenantIdClaim.Value) ? int.Parse(tenantIdClaim.Value) : (int?)null;
}
_dbContext.UserId = userId;
_dbContext.TenantId = tenantId;
var resultContext = await next();
}
}
Para más información mira mi respuesta .
Esta es una pregunta antigua, pero mi caso muestra que mi caso no se discutió aquí.
Lo que más me gusta es la respuesta de Simon_Weaver ( https://.com/a/54411397/2903893 ). Explica en detalle cómo obtener el nombre de usuario utilizando IPrincipal e IIdentity. Esta respuesta es absolutamente correcta y recomiendo usar este enfoque. Sin embargo, durante la depuración me encontré con el problema cuando ASP.NET NO puede completar correctamente el principio de servicio . (o en otras palabras, IPrincipal.Identity.Name es nulo)
Es obvio que para obtener el nombre de usuario MVC Framework debería tomarlo de alguna parte. En el mundo .NET, ASP.NET o ASP.NET Core está utilizando el middleware Open ID Connect. En el escenario simple, las aplicaciones web autentican a un usuario en un navegador web. En este escenario, la aplicación web dirige al navegador del usuario para que inicie sesión en Azure AD. Azure AD devuelve una respuesta de inicio de sesión a través del navegador del usuario, que contiene notificaciones sobre el usuario en un token de seguridad. Para que funcione en el código de su aplicación, deberá proporcionar la autoridad a la que sus aplicaciones web delegan el inicio de sesión. Cuando implementa su aplicación web en Azure Service, el escenario común para cumplir con estos requisitos es configurar la aplicación web: "App Services" -> YourApp -> "Authentication / Authorization" blade -> "App Service Authenticatio" = "On" y así en ( https://github.com/Huachao/azure-content/blob/master/articles/app-service-api/app-service-api-authentication.md ). Creo (esta es mi suposición educada) que, bajo el capó de este proceso, el asistente ajusta la configuración web "principal" de esta aplicación web agregando la misma configuración que muestro en los siguientes párrafos. Básicamente, el problema por el cual este enfoque NO funciona en ASP.NET Core es porque webconfig ignora la configuración de la máquina "principal". (Esto no es 100% seguro, solo doy la mejor explicación que tengo). Por lo tanto, para que funcione, debe configurar esto manualmente en su aplicación.
Aquí hay un artículo que explica cómo configurar su aplicación de manera manual para usar Azure AD. https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/aspnetcore2-2
Paso 1: registre la muestra con su inquilino de Azure AD. (es obvio, no quiero pasar mi tiempo de explicaciones).
Paso 2: en el archivo appsettings.json: reemplace el valor de ClientID con el ID de la aplicación que registró en el portal de registro de aplicaciones en el Paso 1. reemplace el valor de TenantId con common
Paso 3: abra el archivo Startup.cs y en el método ConfigureServices, después de la línea que contiene .AddAzureAD inserte el siguiente código, que permite que su aplicación inicie sesión en los usuarios con el punto final de Azure AD v2.0, que es tanto Work como School y Cuentas personales de Microsoft.
var claim = HttpContext.User.CurrentUserID();
public static class XYZ
{
public static int CurrentUserID(this ClaimsPrincipal claim)
{
var userID = claimsPrincipal.Claims.ToList().Find(r => r.Type ==
"UserID").Value;
return Convert.ToInt32(userID);
}
public static string CurrentUserRole(this ClaimsPrincipal claim)
{
var role = claimsPrincipal.Claims.ToList().Find(r => r.Type ==
"Role").Value;
return role;
}
}
Resumen : He mostrado un posible problema más que podría dar lugar a un error que explica el iniciador del tema. El motivo de este problema son las configuraciones faltantes para Azure AD (middleware de ID abierta). Para resolver este problema, propongo configurar manualmente "Autenticación / Autorización". Se agrega la breve descripción general de cómo configurar esto.
Manera simple que funciona y lo comprobé.
private readonly UserManager<IdentityUser> _userManager;
public CompetitionsController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
var user = await _userManager.GetUserAsync(HttpContext.User);
entonces puede todas las propiedades de estas variables como
user.Email
.
Espero que esto ayude a alguien.
Editar :
Es una cosa aparentemente simple pero un poco complicada debido a los diferentes tipos de sistemas de autenticación en ASP.NET Core.
Actualizo porque algunas personas se están volviendo
null
.
Para la autenticación JWT (probado en ASP.NET Core v3.0.0-preview7):
var email = HttpContext.User.Claims.FirstOrDefault(c => c.Type == "sub")?.Value;
var user = await _userManager.FindByEmailAsync(email);
Mi problema fue acceder al Usuario conectado como un objeto en el archivo cshtml. Teniendo en cuenta que deseaba el usuario en ViewData, este enfoque podría ser útil:
En el archivo cshtml
@using Microsoft.AspNetCore.Identity
@inject UserManager<ApplicationUser> UserManager
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>
@UserManager.FindByNameAsync(UserManager.GetUserName(User)).Result.Email
</title>
</head>
<body>
</body>
</html>
Parece que a partir de ahora (abril de 2017) funciona lo siguiente:
public string LoggedInUser => User.Identity;
Al menos dentro de un
Controller
Quizás no vi la respuesta, pero así es como lo hago.
- .Net Core -> Propiedades -> launchSettings.json
Necesita cambiar estos valores
"windowsAuthentication": true, // needs to be true "anonymousAuthentication": false, // needs to be false
Startup.cs -> ConfigureServices (...)
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
MVC o controlador de API web
private readonly IHttpContextAccessor _httpContextAccessor; //constructor then _httpContextAccessor = httpContextAccessor;
Método del controlador:
string userName = _httpContextAccessor.HttpContext.User.Identity.Name;
El resultado es userName, por ejemplo, = dominio / nombre de usuario
Si está utilizando la identidad escaneada y está utilizando Asp.net Core 2.2+, puede acceder al usuario actual desde una vista como esta:
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
@if (SignInManager.IsSignedIn(User))
{
<p>Hello @User.Identity.Name!</p>
}
else
{
<p>You''re not signed in!</p>
}
Tengo mi solucion
var claim = HttpContext.User.CurrentUserID(); public static class XYZ { public static int CurrentUserID(this ClaimsPrincipal claim) { var userID = claimsPrincipal.Claims.ToList().Find(r => r.Type == "UserID").Value; return Convert.ToInt32(userID); } public static string CurrentUserRole(this ClaimsPrincipal claim) { var role = claimsPrincipal.Claims.ToList().Find(r => r.Type == "Role").Value; return role; } }
Tengo otra forma de obtener el usuario actual en Asp.NET Core, y creo que lo vi en algún lugar aquí, en SO ^^
// Stores UserManager
private readonly UserManager<ApplicationUser> _manager;
// Inject UserManager using dependency injection.
// Works only if you choose "Individual user accounts" during project creation.
public DemoController(UserManager<ApplicationUser> manager)
{
_manager = manager;
}
// You can also just take part after return and use it in async methods.
private async Task<ApplicationUser> GetCurrentUser()
{
return await _manager.GetUserAsync(HttpContext.User);
}
// Generic demo method.
public async Task DemoMethod()
{
var user = await GetCurrentUser();
string userEmail = user.Email; // Here you gets user email
string userId = user.Id;
}
Ese código va al controlador llamado DemoController. No funcionará sin esperar ambos (no se compilará);)
Tengo que decir que me sorprendió bastante que HttpContext sea nulo dentro del constructor.
Estoy seguro de que es por razones de rendimiento.
Han confirmado que usar
IPrincipal
como se describe a continuación lo inyecta en el constructor.
Básicamente está haciendo lo mismo que la respuesta aceptada, pero de una manera más interactiva.
Para cualquiera que encuentre esta pregunta y busque una respuesta al genérico
"¿Cómo obtener un usuario actual?"
solo puede acceder al
User
directamente desde
Controller.User
.
Pero solo puede hacer esto dentro de los métodos de acción (supongo que los controladores no solo se ejecutan con HttpContexts y por razones de rendimiento).
Sin embargo, si lo necesita en el constructor (como lo hizo OP) o necesita crear otros objetos inyectables que necesitan el usuario actual, entonces el siguiente es un mejor enfoque:
Inyecte IPrincipal para obtener el usuario
Primero conozca
IPrincipal
e
IIdentity
public interface IPrincipal
{
IIdentity Identity { get; }
bool IsInRole(string role);
}
public interface IIdentity
{
string AuthenticationType { get; }
bool IsAuthenticated { get; }
string Name { get; }
}
IPrincipal
e
IIdentity
representa el usuario y el nombre de usuario.
Wikipedia te consolará si ''Principal'' suena extraño
.
Es importante darse cuenta de que si lo obtiene de
IHttpContextAccessor.HttpContext.User
,
ControllerBase.User
o
ControllerBase.HttpContext.User
,
obtendrá un objeto que se garantiza que es un objeto
ClaimsPrincipal
que implementa
IPrincipal
.
No hay otro tipo de usuario que ASP.NET use para el
User
este momento (pero eso no quiere decir que otra cosa no pueda implementar
IPrincipal
).
Entonces, si tiene algo que tiene una dependencia del ''nombre de usuario actual'' que desea inyectar, debería inyectar
IPrincipal
y definitivamente no
IHttpContextAccessor
.
Importante:
No pierda el tiempo inyectando
IPrincipal
directamente en su controlador o método de acción; no tiene sentido ya que el
User
ya está disponible para usted allí.
En
startup.cs
:
// Inject IPrincipal
services.AddTransient<IPrincipal>(provider => provider.GetService<IHttpContextAccessor>().HttpContext.User);
Luego, en su objeto DI que necesita al usuario, simplemente inyecte
IPrincipal
para obtener el usuario actual.
Lo más importante aquí es que si está haciendo pruebas unitarias, no necesita enviar un
HttpContext
, pero solo necesita burlarse de algo que representa
IPrincipal
que puede ser
ClaimsPrincipal
.
Una cosa extra importante de la que no estoy 100% seguro.
Si necesita acceder a los reclamos reales de
ClaimsPrincipal
, debe
IPrincipal
a
ClaimsPrincipal
.
Esto está bien ya que sabemos al 100% que en tiempo de ejecución es de ese tipo (ya que eso es
HttpContext.User
).
De hecho, me gusta hacer esto en el constructor, ya que sé con certeza que cualquier
IPrincipal
será un
ClaimsPrincipal
.
Si se está burlando, simplemente
cree un
ClaimsPrincipal
directamente
y páselo a lo que sea necesario.
Exactamente por qué no hay una interfaz para
IClaimsPrincipal
no estoy seguro.
Supongo que MS decidió que
ClaimsPrincipal
era solo una ''colección'' especializada que no garantizaba una interfaz.
Tomar
IdentityUser
también funcionaría.
Este es un objeto de usuario actual y se pueden recuperar todos los valores de usuario.
private readonly UserManager<IdentityUser> _userManager;
public yourController(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
var user = await _userManager.GetUserAsync(HttpContext.User);
User.FindFirst(ClaimTypes.NameIdentifier).Value
EDITAR para constructor
El siguiente código funciona:
public Controller(IHttpContextAccessor httpContextAccessor)
{
var userId = httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value
}
Editar para RTM
Debe registrar
IHttpContextAccessor
:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}