ASP.NET Core Identity - pobierz bieżącego użytkownika

84

Aby uzyskać aktualnie zalogowanego użytkownika w MVC5, wszystko, co musieliśmy zrobić, to:

using Microsoft.AspNet.Identity;
[Authorize]
public IHttpActionResult DoSomething() {
    string currentUserId = User.Identity.GetUserId();
}

Teraz myślę, że w przypadku ASP.NET Core to powinno działać, ale generuje błąd.

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Http;

private readonly UserManager<ApplicationUser> _userManager;
[HttpPost]
[Authorize]
public async Task<IActionResult> StartSession() {
    var curUser = await _userManager.GetUserAsync(HttpContext.User);
}

Jakieś pomysły?

EDYCJA: odpowiedź Gerardo jest na dobrej drodze, ale aby uzyskać rzeczywisty „identyfikator” użytkownika, wydaje się, że działa:

ClaimsPrincipal currentUser = this.User;
var currentUserID = currentUser.FindFirst(ClaimTypes.NameIdentifier).Value;
jbraun
źródło
Jakie dokładnie jest twoje pytanie?
Gerardo Grignoli
Potrzebujesz tylko identyfikatora? Zredagowałem moją odpowiedź, aby dodać, jak ją zdobyć, używając bardziej wyszukanego _userManager.GetUserId (User)
Gerardo Grignoli.
Tak, przede wszystkim potrzebuję identyfikatora z tabeli AspNetUsers lub z sesji. currentUser.FindFirst (ClaimTypes.NameIdentifier) ​​.Value podaje identyfikator przy użyciu Claims. Działa też z UserManager! Dziękuję Gerardo! Czy jeden sposób jest bardziej wydajny niż drugi?
jbraun
UserManager wewnętrznie wykonuje .FindFirst (ClaimTypes.NameIdentifier). Więc to jest to samo pod względem wydajności. Wolę hermetyzację usermanager w celu ułatwienia czytania. Z drugiej strony .GetUserAsync()jest wolniejszy, ponieważ idzie do DB.
Gerardo Grignoli
Całkowicie się zgadzam, Gerardo. Dziękuję Ci !
jbraun

Odpowiedzi:

145

Zakładając, że twój kod znajduje się wewnątrz kontrolera MVC:

public class MyController : Microsoft.AspNetCore.Mvc.Controller

Z Controllerklasy bazowej można pobrać IClaimsPrincipalz Userwłaściwości

System.Security.Claims.ClaimsPrincipal currentUser = this.User;

Możesz sprawdzić roszczenia bezpośrednio (bez podróży w obie strony do bazy danych):

bool IsAdmin = currentUser.IsInRole("Admin");
var id = _userManager.GetUserId(User); // Get user id:

Inne pola można pobrać z jednostki użytkownika bazy danych:

  1. Pobierz menedżera użytkowników za pomocą iniekcji zależności

    private UserManager<ApplicationUser> _userManager;
    
    //class constructor
    public MyController(UserManager<ApplicationUser> userManager)
    {
        _userManager = userManager;
    }
    
  2. I użyj go:

    var user = await _userManager.GetUserAsync(User);
    var email = user.Email;
    
Gerardo Grignoli
źródło
6
A jeśli jestem w klasie usług, a nie w kontrolerze, gdzie nie mam właściwości użytkownika? Jak uzyskać obiekt użytkownika aplikacji uwierzytelnionego użytkownika?
Kirsten
13
@Kirsten Możesz otrzymać IHttpContextAccessorz Dependency Injection, a następnie pobrać Userz kontekstu http. Wystarczy dodać ten param do konstruktora klasy usług: IHttpContextAccessor contextAccessor. Zachowaj akcesor w polu, _contextAccessora następnie w metodach usługi pobierz go _contextAccessor.HttpContext.User.
Gerardo Grignoli
5
@Kirsten Jednak wykonanie tej czynności łączy twoją usługę z ASP.NET Core, dzięki czemu nie będzie można jej używać w innych rodzajach scenariuszy. Może to być dla Ciebie do zaakceptowania lub nie. Rozważę również przekazanie usługi w wywołaniu metody dokładnie tego, co chcesz.
Gerardo Grignoli
2
Popchnąłeś mnie we właściwy sposób: zbudowałem IUserService z GetUser i IsAuthenticated. W mojej aplikacji ASP.NET Core mam implementację userService, która używa IHttpContextAccessor i UserManager <ApplicationUser>. Więc moja usługa userService jest połączona z ASP.NET Core, moje inne usługi nie są.
Kirsten,
2
Nie, nie trafiają do bazy danych. Sprawdzając źródło, nie używają UserStore. github.com/dotnet/corefx/blob/master/src/System.Security.Claims/... github.com/aspnet/Identity/blob/…
Gerardo Grignoli
34

Jeśli korzystasz z uwierzytelniania za pomocą tokenów łożysk, powyższe przykłady nie zwracają użytkownika aplikacji.

Zamiast tego użyj tego:

ClaimsPrincipal currentUser = this.User;
var currentUserName = currentUser.FindFirst(ClaimTypes.NameIdentifier).Value;
ApplicationUser user = await _userManager.FindByNameAsync(currentUserName);

Działa to w apsnetcore 2.0. Nie próbowałem we wcześniejszych wersjach.

Greg Gum
źródło
Moje tokeny są sprawdzane, ale nazwa główna i oświadczenia nie są ustawiane. Dzieje się tak w dotnet core 2.1 i 2.2
ps2goat
@ ps2goat, utworzyłbym nowe pytanie, aby rozwiązać ten problem i dołączyć kod, który tworzy token.
Greg Gum
1
Rozgryzłem to. Nie mamy domyślnego schematu, ponieważ mamy 5 różnych schematów. Musiałem dodać oprogramowanie pośredniczące, aby zdekodować token i przypisać roszczenia do bieżącego użytkownika. Powinienem prawdopodobnie napisać pytanie i odpowiedzieć innym, ponieważ moja sytuacja jest bardzo mało treści.
ps2goat
1
ClaimTypes.Name pobiera nazwę użytkownika, której szukałem zamiast identyfikatora. ClaimTypes.NameIdentifier pobiera identyfikator użytkownika. Na wszelki wypadek, gdyby ktoś to zobaczył i zastanawia się, jak zdobyć nazwę użytkownika. Dzięki za odpowiedź Greg!
Matthew Zourelias
7

W kontekście utworzyłem projekt przy użyciu szablonu aplikacji sieci Web ASP.NET Core 2. Następnie wybierz aplikację internetową (MVC), a następnie naciśnij przycisk Zmień uwierzytelnianie i wybierz Indywidualne konta użytkowników.

Na podstawie tego szablonu zbudowano wiele infrastruktury. Znajdź ManageControllerw folderze Kontrolery.

Ten ManageControllerkonstruktor klasy wymaga wypełnienia tej zmiennej UserManager:

private readonly UserManager<ApplicationUser> _userManager;

Następnie przyjrzyj się metodzie [HttpPost] Index w tej klasie. Bieżącego użytkownika uzyskują w ten sposób:

var user = await _userManager.GetUserAsync(User);

Jako dodatkowa uwaga, w tym miejscu chcesz zaktualizować dowolne pola niestandardowe do profilu użytkownika dodanego do tabeli AspNetUsers. Dodaj pola do widoku, a następnie prześlij te wartości do IndexViewModel, który jest następnie przesyłany do tej metody Post. Dodałem ten kod po domyślnej logice do ustawienia adresu e-mail i numeru telefonu:

user.FirstName = model.FirstName;
user.LastName = model.LastName;
user.Address1 = model.Address1;
user.Address2 = model.Address2;
user.City = model.City;
user.State = model.State;
user.Zip = model.Zip;
user.Company = model.Company;
user.Country = model.Country;
user.SetDisplayName();
user.SetProfileID();

_dbContext.Attach(user).State = EntityState.Modified;
_dbContext.SaveChanges();
Joel Palmer
źródło
5

W .NET Core 2.0 użytkownik już istnieje jako część podstawowego kontrolera dziedziczonego. Po prostu użyj użytkownika tak, jak zwykle lub przejdź do dowolnego kodu repozytorium.

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Policy = "TENANT")]
[HttpGet("issue-type-selection"), Produces("application/json")]
public async Task<IActionResult> IssueTypeSelection()
{
    try
    {
        return new ObjectResult(await _item.IssueTypeSelection(User));
    }
    catch (ExceptionNotFound)
    {
        Response.StatusCode = (int)HttpStatusCode.BadRequest;
        return Json(new
        {
            error = "invalid_grant",
            error_description = "Item Not Found"
        });
    }
}

Stąd go dziedziczy

#region Assembly Microsoft.AspNetCore.Mvc.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
// C:\Users\BhailDa\.nuget\packages\microsoft.aspnetcore.mvc.core\2.0.0\lib\netstandard2.0\Microsoft.AspNetCore.Mvc.Core.dll
#endregion

using System;
using System.IO;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.Net.Http.Headers;

namespace Microsoft.AspNetCore.Mvc
{
    //
    // Summary:
    //     A base class for an MVC controller without view support.
    [Controller]
    public abstract class ControllerBase
    {
        protected ControllerBase();

        //
        // Summary:
        //     Gets the System.Security.Claims.ClaimsPrincipal for user associated with the
        //     executing action.
        public ClaimsPrincipal User { get; }
Bhail
źródło
2

Jeśli ktoś jest zainteresowany, to zadziałało dla mnie. Mam niestandardową tożsamość, która używa int dla klucza podstawowego, więc zastąpiłem metodę GetUserAsync

Zastąp GetUserAsync

public override Task<User> GetUserAsync(ClaimsPrincipal principal)
{
    var userId = GetUserId(principal);
    return FindByNameAsync(userId);
}

Uzyskaj użytkownika tożsamości

var user = await _userManager.GetUserAsync(User);

Jeśli używasz zwykłego klucza podstawowego Guid, nie musisz zastępować GetUserAsync. Wszystko to przy założeniu, że token jest poprawnie skonfigurowany.

public async Task<string> GenerateTokenAsync(string email)
{
    var user = await _userManager.FindByEmailAsync(email);
    var tokenHandler = new JwtSecurityTokenHandler();
    var key = Encoding.ASCII.GetBytes(_tokenProviderOptions.SecretKey);

    var userRoles = await _userManager.GetRolesAsync(user);
    var roles = userRoles.Select(o => new Claim(ClaimTypes.Role, o));

    var claims = new[]
    {
        new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString(CultureInfo.CurrentCulture)),
        new Claim(JwtRegisteredClaimNames.GivenName, user.FirstName),
        new Claim(JwtRegisteredClaimNames.FamilyName, user.LastName),
        new Claim(JwtRegisteredClaimNames.Email, user.Email),
    }
    .Union(roles);

    var tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(claims),
        Expires = DateTime.UtcNow.AddHours(_tokenProviderOptions.Expires),
        SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
    };

    var token = tokenHandler.CreateToken(tokenDescriptor);

    return Task.FromResult(new JwtSecurityTokenHandler().WriteToken(token)).Result;
}
Dblock247
źródło
1
private readonly UserManager<AppUser> _userManager;

 public AccountsController(UserManager<AppUser> userManager)
 {
            _userManager = userManager;
 }

[Authorize(Policy = "ApiUser")]
[HttpGet("api/accounts/GetProfile", Name = "GetProfile")]
public async Task<IActionResult> GetProfile()
{
   var userId = ((ClaimsIdentity)User.Identity).FindFirst("Id").Value;
   var user = await _userManager.FindByIdAsync(userId);

   ProfileUpdateModel model = new ProfileUpdateModel();
   model.Email = user.Email;
   model.FirstName = user.FirstName;
   model.LastName = user.LastName;
   model.PhoneNumber = user.PhoneNumber;

   return new OkObjectResult(model);
}
Rokive
źródło
0

Umieściłem coś takiego w mojej klasie kontrolera i zadziałało:

IdentityUser user = await userManager.FindByNameAsync(HttpContext.User.Identity.Name);

gdzie userManager jest instancją klasy Microsoft.AspNetCore.Identity.UserManager (z całą dziwną konfiguracją, która się z tym wiąże).

Marko
źródło