Zachování dalších deklarací identity a tokenů od externích poskytovatelů v ASP.NET Core

Aplikace ASP.NET Core může navázat další deklarace identity a tokeny od externích zprostředkovatelů ověřování, jako je Facebook, Google, Microsoft a Twitter. Každý poskytovatel odhalí různé informace o uživatelích na své platformě, ale vzor pro příjem a transformaci uživatelských dat na další deklarace identity je stejný.

Požadavky

Rozhodněte se, kteří externí zprostředkovatelé ověřování budou v aplikaci podporovat. Pro každého zprostředkovatele zaregistrujte aplikaci a získejte ID klienta a tajný klíč klienta. Další informace najdete v tématu Ověřování Facebookem a Googlem v ASP.NET Core. Ukázková aplikace používá zprostředkovatele ověřování Google.

Nastavení ID klienta a tajného klíče klienta

Zprostředkovatel ověřování OAuth vytvoří vztah důvěryhodnosti s aplikací pomocí ID klienta a tajného klíče klienta. ID klienta a hodnoty tajných kódů klienta se pro aplikaci vytvoří externím zprostředkovatelem ověřování, když je aplikace zaregistrovaná u zprostředkovatele. Každý externí zprostředkovatel, který aplikace používá, musí být nakonfigurován nezávisle na ID klienta a tajném klíči klienta poskytovatele. Další informace najdete v tématech o externím poskytovateli ověřování, která platí:

Volitelné deklarace identity odeslané v ID nebo přístupovém tokenu od zprostředkovatele ověřování se obvykle konfigurují na online portálu poskytovatele. Například Microsoft Azure Active Directory (AAD) umožňuje přiřazovat volitelné deklarace identity tokenu ID aplikace v okně konfigurace tokenu registrace aplikace. Další informace najdete v tématu Postupy: Poskytnutí volitelných deklarací identity pro vaši aplikaci (dokumentace k Azure). Pro ostatní poskytovatele se obraťte na externí sady dokumentace.

Ukázková aplikace nakonfiguruje zprostředkovatele ověřování Google pomocí ID klienta a tajného klíče klienta poskytovaného Googlem:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using WebGoogOauth.Data;

var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;

builder.Services.AddAuthentication().AddGoogle(googleOptions =>
{
    googleOptions.ClientId = configuration["Authentication:Google:ClientId"];
    googleOptions.ClientSecret = configuration["Authentication:Google:ClientSecret"];

    googleOptions.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
    googleOptions.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");

    googleOptions.SaveTokens = true;

    googleOptions.Events.OnCreatingTicket = ctx =>
    {
        List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();

        tokens.Add(new AuthenticationToken()
        {
            Name = "TicketCreated",
            Value = DateTime.UtcNow.ToString()
        });

        ctx.Properties.StoreTokens(tokens);

        return Task.CompletedTask;
    };
});

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => 
                                  options.SignIn.RequireConfirmedAccount = true)
                                 .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

// Remaining code removed for brevity.

Vytvoření oboru ověřování

Zadejte seznam oprávnění k načtení od zprostředkovatele zadáním .Scope Obory ověřování pro běžné externí zprostředkovatele se zobrazují v následující tabulce.

Poskytovatel Obor
Facebook https://www.facebook.com/dialog/oauth
Google profile, email, openid
Microsoft https://login.microsoftonline.com/common/oauth2/v2.0/authorize
Twitter https://api.twitter.com/oauth/authenticate

V ukázkové aplikaci, Google profile, emaila openid rozsahy jsou automaticky přidány rozhraním, když AddGoogle je volána AuthenticationBuilderna . Pokud aplikace vyžaduje další obory, přidejte je do možností. V následujícím příkladu se přidá obor Google https://www.googleapis.com/auth/user.birthday.read pro načtení narozenin uživatele:

options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");

Mapování uživatelských datových klíčů a vytváření deklarací identity

V možnostech poskytovatele zadejte MapJsonKey nebo MapJsonSubKey pro každý klíč nebo podklíč v datech uživatele ON externího poskytovatele JSpro identitu aplikace, která se má číst při přihlášení. Další informace o typech deklarací identity najdete v tématu ClaimTypes.

Ukázková aplikace vytvoří deklarace identity národního prostředí (urn:google:locale) a obrázku locale (urn:google:picture) z uživatelských dat Google a picture klíčů:

builder.Services.AddAuthentication().AddGoogle(googleOptions =>
{
    googleOptions.ClientId = configuration["Authentication:Google:ClientId"];
    googleOptions.ClientSecret = configuration["Authentication:Google:ClientSecret"];

    googleOptions.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
    googleOptions.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");

    googleOptions.SaveTokens = true;

    googleOptions.Events.OnCreatingTicket = ctx =>
    {
        List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();

        tokens.Add(new AuthenticationToken()
        {
            Name = "TicketCreated",
            Value = DateTime.UtcNow.ToString()
        });

        ctx.Properties.StoreTokens(tokens);

        return Task.CompletedTask;
    };
});

V Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsyncaplikaci IdentityUserApplicationUserse přihlásí () pomocí SignInAsyncaplikace . Během procesu UserManager<TUser> přihlášení může ukládat ApplicationUser deklarace identity pro uživatelská data dostupná na webu Principal.

V ukázkové aplikaci OnPostConfirmationAsync vytvoří (Account/ExternalLogin.cshtml.cs) deklarace identity národního prostředí (urn:google:locale) a obrázku (urn:google:picture) pro přihlášené ApplicationUser, včetně deklarace identity pro GivenName:

public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    // Get the information about the user from the external login provider
    var info = await _signInManager.GetExternalLoginInfoAsync();
    if (info == null)
    {
        ErrorMessage = "Error loading external login information during confirmation.";
        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }

    if (ModelState.IsValid)
    {
        var user = CreateUser();

        await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
        await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);

        var result = await _userManager.CreateAsync(user);
        if (result.Succeeded)
        {
            result = await _userManager.AddLoginAsync(user, info);
            if (result.Succeeded)
            {
                _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);

                // If they exist, add claims to the user for:
                //    Given (first) name
                //    Locale
                //    Picture
                if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
                {
                    await _userManager.AddClaimAsync(user,
                        info.Principal.FindFirst(ClaimTypes.GivenName));
                }

                if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
                {
                    await _userManager.AddClaimAsync(user,
                        info.Principal.FindFirst("urn:google:locale"));
                }

                if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
                {
                    await _userManager.AddClaimAsync(user,
                        info.Principal.FindFirst("urn:google:picture"));
                }

                // Include the access token in the properties
                // using Microsoft.AspNetCore.Authentication;
                var props = new AuthenticationProperties();
                props.StoreTokens(info.AuthenticationTokens);
                props.IsPersistent = false;

                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                var callbackUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { area = "Identity", userId = userId, code = code },
                    protocol: Request.Scheme);

                await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                    $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

                // If account confirmation is required, we need to show the link if we don't have a real email sender
                if (_userManager.Options.SignIn.RequireConfirmedAccount)
                {
                    return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
                }

                await _signInManager.SignInAsync(user, props, info.LoginProvider);
                return LocalRedirect(returnUrl);
            }
        }
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    ProviderDisplayName = info.ProviderDisplayName;
    ReturnUrl = returnUrl;
    return Page();
}

Ve výchozím nastavení jsou deklarace identity uživatele uloženy v ověřování cookie. Pokud je ověřování cookie příliš velké, může to způsobit selhání aplikace, protože:

  • Prohlížeč zjistí, že cookie záhlaví je příliš dlouhé.
  • Celková velikost požadavku je příliš velká.

Pokud se ke zpracování žádostí uživatelů vyžaduje velké množství uživatelských dat:

  • Omezte počet a velikost deklarací identity uživatelů pro zpracování požadavků jenom na to, co aplikace vyžaduje.
  • K ukládání identity mezi požadavky použijte vlastní ITicketStoreCookie middlewar SessionStore ověřování. Při odesílání malého klíče identifikátoru relace klientovi zachováte velké množství informací o identitě na serveru.

Uložení přístupového tokenu

SaveTokens definuje, zda mají být po úspěšné autorizaci uloženy AuthenticationProperties přístupové a obnovovací tokeny. SaveTokens je ve výchozím nastavení nastavena tak false , aby se zmenšila velikost konečného ověřování cookie.

Ukázková aplikace nastaví hodnotu SaveTokens in trueGoogleOptions:

builder.Services.AddAuthentication().AddGoogle(googleOptions =>
{
    googleOptions.ClientId = configuration["Authentication:Google:ClientId"];
    googleOptions.ClientSecret = configuration["Authentication:Google:ClientSecret"];

    googleOptions.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
    googleOptions.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");

    googleOptions.SaveTokens = true;

    googleOptions.Events.OnCreatingTicket = ctx =>
    {
        List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();

        tokens.Add(new AuthenticationToken()
        {
            Name = "TicketCreated",
            Value = DateTime.UtcNow.ToString()
        });

        ctx.Properties.StoreTokens(tokens);

        return Task.CompletedTask;
    };
});

Při OnPostConfirmationAsync spuštění uložte přístupový token (ExternalLoginInfo.AuthenticationTokens) z externího zprostředkovatele do objektu ApplicationUser's AuthenticationProperties.

Ukázková aplikace uloží přístupový token do OnPostConfirmationAsync (registrace nového uživatele) a OnGetCallbackAsync (dříve zaregistrovaného uživatele) do Account/ExternalLogin.cshtml.cs:

public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    // Get the information about the user from the external login provider
    var info = await _signInManager.GetExternalLoginInfoAsync();
    if (info == null)
    {
        ErrorMessage = "Error loading external login information during confirmation.";
        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }

    if (ModelState.IsValid)
    {
        var user = CreateUser();

        await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
        await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);

        var result = await _userManager.CreateAsync(user);
        if (result.Succeeded)
        {
            result = await _userManager.AddLoginAsync(user, info);
            if (result.Succeeded)
            {
                _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);

                // If they exist, add claims to the user for:
                //    Given (first) name
                //    Locale
                //    Picture
                if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
                {
                    await _userManager.AddClaimAsync(user,
                        info.Principal.FindFirst(ClaimTypes.GivenName));
                }

                if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
                {
                    await _userManager.AddClaimAsync(user,
                        info.Principal.FindFirst("urn:google:locale"));
                }

                if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
                {
                    await _userManager.AddClaimAsync(user,
                        info.Principal.FindFirst("urn:google:picture"));
                }

                // Include the access token in the properties
                // using Microsoft.AspNetCore.Authentication;
                var props = new AuthenticationProperties();
                props.StoreTokens(info.AuthenticationTokens);
                props.IsPersistent = false;

                var userId = await _userManager.GetUserIdAsync(user);
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
                var callbackUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { area = "Identity", userId = userId, code = code },
                    protocol: Request.Scheme);

                await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                    $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

                // If account confirmation is required, we need to show the link if we don't have a real email sender
                if (_userManager.Options.SignIn.RequireConfirmedAccount)
                {
                    return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
                }

                await _signInManager.SignInAsync(user, props, info.LoginProvider);
                return LocalRedirect(returnUrl);
            }
        }
        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    ProviderDisplayName = info.ProviderDisplayName;
    ReturnUrl = returnUrl;
    return Page();
}

Poznámka

Informace o předávání tokenů Razor komponentám aplikace na straně serveru najdete v tématu Další scénáře zabezpečení na straně Blazor serveru ASP.NET CoreBlazor.

Přidání dalších vlastních tokenů

Abychom si ukázali, jak přidat vlastní token, který je uložený jako součást SaveTokens, ukázková aplikace přidá AuthenticationToken s aktuální DateTime hodnotou pro AuthenticationToken.Name:TicketCreated

builder.Services.AddAuthentication().AddGoogle(googleOptions =>
{
    googleOptions.ClientId = configuration["Authentication:Google:ClientId"];
    googleOptions.ClientSecret = configuration["Authentication:Google:ClientSecret"];

    googleOptions.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
    googleOptions.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");

    googleOptions.SaveTokens = true;

    googleOptions.Events.OnCreatingTicket = ctx =>
    {
        List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();

        tokens.Add(new AuthenticationToken()
        {
            Name = "TicketCreated",
            Value = DateTime.UtcNow.ToString()
        });

        ctx.Properties.StoreTokens(tokens);

        return Task.CompletedTask;
    };
});

Vytvoření a přidání deklarací identity

Architektura poskytuje běžné akce a metody rozšíření pro vytváření a přidávání deklarací identity do kolekce. Další informace naleznete v tématu ClaimActionCollectionMapExtensions a ClaimActionCollectionUniqueExtensions.

Uživatelé mohou definovat vlastní akce odvozením ClaimAction a implementací abstraktní Run metody.

Další informace najdete na webu Microsoft.AspNetCore.Authentication.OAuth.Claims.

Přidání a aktualizace deklarací identity uživatelů

Deklarace identity se zkopírují z externích zprostředkovatelů do uživatelské databáze při první registraci, ne při přihlášení. Pokud jsou v aplikaci povoleny další deklarace identity poté, co se uživatel zaregistruje k použití aplikace, zavolejte SignInManager.RefreshSignInAsync na uživatele, aby vynutil generování nového ověřování cookie.

Ve vývojovém prostředí pracujícím s testovacími uživatelskými účty odstraňte a znovu vytvořte uživatelský účet. V produkčních systémech je možné nové deklarace identity přidané do aplikace znovu vyplňovat do uživatelských účtů. Po vygenerování ExternalLogin stránky do aplikace na adrese Areas/Pages/Identity/Account/Manage, přidejte do ExternalLogin.cshtml.csExternalLoginModel souboru následující kód.

Přidejte slovník přidaných deklarací identity. K uložení typů deklarací identity použijte klíče slovníku a hodnoty použijte k uložení výchozí hodnoty. Na začátek třídy přidejte následující řádek. Následující příklad předpokládá, že se pro obrázek Google uživatele přidá jedna deklarace identity s obecným obrázkem headshot jako výchozí hodnotou:

private readonly IReadOnlyDictionary<string, string> _claimsToSync =
     new Dictionary<string, string>()
     {
             { "urn:google:picture", "https://localhost:5001/headshot.png" },
     };

Nahraďte výchozí kód OnGetCallbackAsync metody následujícím kódem. Kód prochází slovník deklarací identity. Deklarace identity se přidají (zaplní) nebo se aktualizují pro každého uživatele. Při přidání nebo aktualizaci deklarací identity se přihlášení uživatele aktualizuje pomocí SignInManager<TUser>vlastnosti existujícího ověřování (AuthenticationProperties).

private readonly IReadOnlyDictionary<string, string> _claimsToSync =
     new Dictionary<string, string>()
     {
             { "urn:google:picture", "https://localhost:5001/headshot.png" },
     };

public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    if (remoteError != null)
    {
        ErrorMessage = $"Error from external provider: {remoteError}";
        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }
    var info = await _signInManager.GetExternalLoginInfoAsync();
    if (info == null)
    {
        ErrorMessage = "Error loading external login information.";
        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }

    // Sign in the user with this external login provider if the user already has a login.
    var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
    if (result.Succeeded)
    {
        _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
        if (_claimsToSync.Count > 0)
        {
            var user = await _userManager.FindByLoginAsync(info.LoginProvider,
                info.ProviderKey);
            var userClaims = await _userManager.GetClaimsAsync(user);
            bool refreshSignIn = false;

            foreach (var addedClaim in _claimsToSync)
            {
                var userClaim = userClaims
                    .FirstOrDefault(c => c.Type == addedClaim.Key);

                if (info.Principal.HasClaim(c => c.Type == addedClaim.Key))
                {
                    var externalClaim = info.Principal.FindFirst(addedClaim.Key);

                    if (userClaim == null)
                    {
                        await _userManager.AddClaimAsync(user,
                            new Claim(addedClaim.Key, externalClaim.Value));
                        refreshSignIn = true;
                    }
                    else if (userClaim.Value != externalClaim.Value)
                    {
                        await _userManager
                            .ReplaceClaimAsync(user, userClaim, externalClaim);
                        refreshSignIn = true;
                    }
                }
                else if (userClaim == null)
                {
                    // Fill with a default value
                    await _userManager.AddClaimAsync(user, new Claim(addedClaim.Key,
                        addedClaim.Value));
                    refreshSignIn = true;
                }
            }

            if (refreshSignIn)
            {
                await _signInManager.RefreshSignInAsync(user);
            }
        }

        return LocalRedirect(returnUrl);
    }
    if (result.IsLockedOut)
    {
        return RedirectToPage("./Lockout");
    }
    else
    {
        // If the user does not have an account, then ask the user to create an account.
        ReturnUrl = returnUrl;
        ProviderDisplayName = info.ProviderDisplayName;
        if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
        {
            Input = new InputModel
            {
                Email = info.Principal.FindFirstValue(ClaimTypes.Email)
            };
        }
        return Page();
    }
}

Podobný přístup se použije, když se deklarace identity změní, když je uživatel přihlášený, ale krok zpětného vyplňování se nevyžaduje. Chcete-li aktualizovat deklarace identity uživatele, zavolejte na uživatele následující:

Odebrání akcí deklarací identity a deklarací identity

ClaimActionCollection.Remove(String) odebere všechny akce deklarace identity pro danou ClaimType z kolekce. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) odstraní deklaraci dané ClaimType identity. DeleteClaimse primárně používá s OpenID Připojení (OIDC) k odebrání deklarací generovaných protokolem.

Ukázkový výstup aplikace

Spusťte ukázkovou aplikaci a vyberte odkaz MyClaims:

User Claims

http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
    9b342344f-7aab-43c2-1ac1-ba75912ca999
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
    someone@gmail.com
AspNet.Identity.SecurityStamp
    7D4312MOWRYYBFI1KXRPHGOSTBVWSFDE
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
    Judy
urn:google:locale
    en
urn:google:picture
    https://lh4.googleusercontent.com/-XXXXXX/XXXXXX/XXXXXX/XXXXXX/photo.jpg

Authentication Properties

.Token.access_token
    yc23.AlvoZqz56...1lxltXV7D-ZWP9
.Token.token_type
    Bearer
.Token.expires_at
    2019-04-11T22:14:51.0000000+00:00
.Token.TicketCreated
    4/11/2019 9:14:52 PM
.TokenNames
    access_token;token_type;expires_at;TicketCreated
.persistent
.issued
    Thu, 11 Apr 2019 20:51:06 GMT
.expires
    Thu, 25 Apr 2019 20:51:06 GMT

Předávání informací o požadavcích pomocí proxy serveru nebo nástroje pro vyrovnávání zatížení

Pokud je aplikace nasazená za proxy serverem nebo nástrojem pro vyrovnávání zatížení, mohou se některé z původních informací o požadavku předávat do aplikace v hlavičce požadavku. Tyto informace obvykle zahrnují zabezpečené schéma požadavků (https), hostitele a IP adresu klienta. Aplikace tyto hlavičky požadavků nenačítají automaticky, aby zjistily a využily informace o původním požadavku.

Schéma se používá při generování odkazů, které ovlivňuje tok ověřování s externími zprostředkovateli. Ztráta zabezpečeného schématu (https) způsobí, že aplikace generuje nesprávné a nezabezpečené adresy URL pro přesměrování.

Pomocí middlewaru Forwarded Headers zpřístupníte aplikaci informace o původním požadavku pro zpracování požadavků.

Další informace najdete v tématu Konfigurace ASP.NET Core pro práci s proxy servery a nástroji pro vyrovnávání zatížení.

Zobrazení nebo stažení ukázkového kódu (postup stažení)

Aplikace ASP.NET Core může navázat další deklarace identity a tokeny od externích zprostředkovatelů ověřování, jako je Facebook, Google, Microsoft a Twitter. Každý poskytovatel odhalí různé informace o uživatelích na své platformě, ale vzor pro příjem a transformaci uživatelských dat na další deklarace identity je stejný.

Zobrazení nebo stažení ukázkového kódu (postup stažení)

Požadavky

Rozhodněte se, kteří externí zprostředkovatelé ověřování budou v aplikaci podporovat. Pro každého zprostředkovatele zaregistrujte aplikaci a získejte ID klienta a tajný klíč klienta. Další informace najdete v tématu Ověřování Facebookem a Googlem v ASP.NET Core. Ukázková aplikace používá zprostředkovatele ověřování Google.

Nastavení ID klienta a tajného klíče klienta

Zprostředkovatel ověřování OAuth vytvoří vztah důvěryhodnosti s aplikací pomocí ID klienta a tajného klíče klienta. ID klienta a hodnoty tajných kódů klienta se pro aplikaci vytvoří externím zprostředkovatelem ověřování, když je aplikace zaregistrovaná u zprostředkovatele. Každý externí zprostředkovatel, který aplikace používá, musí být nakonfigurován nezávisle na ID klienta a tajném klíči klienta poskytovatele. Další informace najdete v tématech externího zprostředkovatele ověřování, která platí pro váš scénář:

Volitelné deklarace identity odeslané v ID nebo přístupovém tokenu od zprostředkovatele ověřování se obvykle konfigurují na online portálu poskytovatele. Například Microsoft Azure Active Directory (AAD) umožňuje přiřadit volitelné deklarace identity tokenu aplikace v okně konfigurace tokenu registrace aplikace. Další informace najdete v tématu Postupy: Poskytnutí volitelných deklarací identity pro vaši aplikaci (dokumentace k Azure). Pro ostatní poskytovatele se obraťte na externí sady dokumentace.

Ukázková aplikace nakonfiguruje zprostředkovatele ověřování Google pomocí ID klienta a tajného klíče klienta poskytovaného Googlem:

services.AddAuthentication().AddGoogle(options =>
{
    // Provide the Google Client ID
    options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"

    // Provide the Google Client Secret
    options.ClientSecret = "{Client Secret}";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"

    options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
    options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
    options.SaveTokens = true;

    options.Events.OnCreatingTicket = ctx =>
    {
        List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList(); 

        tokens.Add(new AuthenticationToken()
        {
            Name = "TicketCreated", 
            Value = DateTime.UtcNow.ToString()
        });

        ctx.Properties.StoreTokens(tokens);

        return Task.CompletedTask;
    };
});

Vytvoření oboru ověřování

Zadejte seznam oprávnění k načtení od zprostředkovatele zadáním .Scope Obory ověřování pro běžné externí zprostředkovatele se zobrazují v následující tabulce.

Poskytovatel Obor
Facebook https://www.facebook.com/dialog/oauth
Google profile, email, openid
Microsoft https://login.microsoftonline.com/common/oauth2/v2.0/authorize
Twitter https://api.twitter.com/oauth/authenticate

V ukázkové aplikaci, Google profile, emaila openid rozsahy jsou automaticky přidány rozhraním, když AddGoogle je volána AuthenticationBuilderna . Pokud aplikace vyžaduje další obory, přidejte je do možností. V následujícím příkladu se přidá obor Google https://www.googleapis.com/auth/user.birthday.read pro načtení narozenin uživatele:

options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");

Mapování uživatelských datových klíčů a vytváření deklarací identity

V možnostech zprostředkovatele zadejte MapJsonKey klíč nebo MapJsonSubKey podklíč v datech uživatele ON externího poskytovatele JSpro identitu aplikace, která se má číst při přihlášení. Další informace o typech deklarací identity najdete v tématu ClaimTypes.

Ukázková aplikace vytvoří deklarace identity národního prostředí (urn:google:locale) a obrázku locale (urn:google:picture) z uživatelských dat Google a picture klíčů:

services.AddAuthentication().AddGoogle(options =>
{
    // Provide the Google Client ID
    options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"

    // Provide the Google Client Secret
    options.ClientSecret = "{Client Secret}";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"

    options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
    options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
    options.SaveTokens = true;

    options.Events.OnCreatingTicket = ctx =>
    {
        List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList(); 

        tokens.Add(new AuthenticationToken()
        {
            Name = "TicketCreated", 
            Value = DateTime.UtcNow.ToString()
        });

        ctx.Properties.StoreTokens(tokens);

        return Task.CompletedTask;
    };
});

V Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsyncaplikaci IdentityUserApplicationUserse přihlásí () pomocí SignInAsyncaplikace . Během procesu UserManager<TUser> přihlášení může ukládat ApplicationUser deklarace identity pro uživatelská data dostupná na webu Principal.

V ukázkové aplikaci OnPostConfirmationAsync vytvoří (Account/ExternalLogin.cshtml.cs) deklarace identity národního prostředí (urn:google:locale) a obrázku (urn:google:picture) pro přihlášené ApplicationUser, včetně deklarace identity pro GivenName:

public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    // Get the information about the user from the external login provider
    var info = await _signInManager.GetExternalLoginInfoAsync();

    if (info == null)
    {
        ErrorMessage = 
            "Error loading external login information during confirmation.";

        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }

    if (ModelState.IsValid)
    {
        var user = new IdentityUser
        {
            UserName = Input.Email, 
            Email = Input.Email 
        };

        var result = await _userManager.CreateAsync(user);

        if (result.Succeeded)
        {
            result = await _userManager.AddLoginAsync(user, info);

            if (result.Succeeded)
            {
                // If they exist, add claims to the user for:
                //    Given (first) name
                //    Locale
                //    Picture
                if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
                {
                    await _userManager.AddClaimAsync(user, 
                        info.Principal.FindFirst(ClaimTypes.GivenName));
                }

                if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
                {
                    await _userManager.AddClaimAsync(user, 
                        info.Principal.FindFirst("urn:google:locale"));
                }

                if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
                {
                    await _userManager.AddClaimAsync(user, 
                        info.Principal.FindFirst("urn:google:picture"));
                }

                // Include the access token in the properties
                var props = new AuthenticationProperties();
                props.StoreTokens(info.AuthenticationTokens);
                props.IsPersistent = true;

                await _signInManager.SignInAsync(user, props);

                _logger.LogInformation(
                    "User created an account using {Name} provider.", 
                    info.LoginProvider);

                return LocalRedirect(returnUrl);
            }
        }

        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    LoginProvider = info.LoginProvider;
    ReturnUrl = returnUrl;
    return Page();
}

Ve výchozím nastavení jsou deklarace identity uživatele uloženy v ověřování cookie. Pokud je ověřování cookie příliš velké, může to způsobit selhání aplikace, protože:

  • Prohlížeč zjistí, že cookie záhlaví je příliš dlouhé.
  • Celková velikost požadavku je příliš velká.

Pokud se ke zpracování žádostí uživatelů vyžaduje velké množství uživatelských dat:

  • Omezte počet a velikost deklarací identity uživatelů pro zpracování požadavků jenom na to, co aplikace vyžaduje.
  • K ukládání identity mezi požadavky použijte vlastní ITicketStoreCookie middlewar SessionStore ověřování. Při odesílání malého klíče identifikátoru relace klientovi zachováte velké množství informací o identitě na serveru.

Uložení přístupového tokenu

SaveTokens definuje, zda mají být po úspěšné autorizaci uloženy AuthenticationProperties přístupové a obnovovací tokeny. SaveTokens je ve výchozím nastavení nastavena tak false , aby se zmenšila velikost konečného ověřování cookie.

Ukázková aplikace nastaví hodnotu SaveTokens in trueGoogleOptions:

services.AddAuthentication().AddGoogle(options =>
{
    // Provide the Google Client ID
    options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"

    // Provide the Google Client Secret
    options.ClientSecret = "{Client Secret}";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"

    options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
    options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
    options.SaveTokens = true;

    options.Events.OnCreatingTicket = ctx =>
    {
        List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList(); 

        tokens.Add(new AuthenticationToken()
        {
            Name = "TicketCreated", 
            Value = DateTime.UtcNow.ToString()
        });

        ctx.Properties.StoreTokens(tokens);

        return Task.CompletedTask;
    };
});

Při OnPostConfirmationAsync spuštění uložte přístupový token (ExternalLoginInfo.AuthenticationTokens) z externího zprostředkovatele do objektu ApplicationUser's AuthenticationProperties.

Ukázková aplikace uloží přístupový token do OnPostConfirmationAsync (registrace nového uživatele) a OnGetCallbackAsync (dříve zaregistrovaného uživatele) do Account/ExternalLogin.cshtml.cs:

public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    // Get the information about the user from the external login provider
    var info = await _signInManager.GetExternalLoginInfoAsync();

    if (info == null)
    {
        ErrorMessage = 
            "Error loading external login information during confirmation.";

        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }

    if (ModelState.IsValid)
    {
        var user = new IdentityUser
        {
            UserName = Input.Email, 
            Email = Input.Email 
        };

        var result = await _userManager.CreateAsync(user);

        if (result.Succeeded)
        {
            result = await _userManager.AddLoginAsync(user, info);

            if (result.Succeeded)
            {
                // If they exist, add claims to the user for:
                //    Given (first) name
                //    Locale
                //    Picture
                if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
                {
                    await _userManager.AddClaimAsync(user, 
                        info.Principal.FindFirst(ClaimTypes.GivenName));
                }

                if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
                {
                    await _userManager.AddClaimAsync(user, 
                        info.Principal.FindFirst("urn:google:locale"));
                }

                if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
                {
                    await _userManager.AddClaimAsync(user, 
                        info.Principal.FindFirst("urn:google:picture"));
                }

                // Include the access token in the properties
                var props = new AuthenticationProperties();
                props.StoreTokens(info.AuthenticationTokens);
                props.IsPersistent = true;

                await _signInManager.SignInAsync(user, props);

                _logger.LogInformation(
                    "User created an account using {Name} provider.", 
                    info.LoginProvider);

                return LocalRedirect(returnUrl);
            }
        }

        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    LoginProvider = info.LoginProvider;
    ReturnUrl = returnUrl;
    return Page();
}

Poznámka

Informace o předávání tokenů Razor komponentám aplikace na straně serveru najdete v tématu Další scénáře zabezpečení na straně Blazor serveru ASP.NET CoreBlazor.

Přidání dalších vlastních tokenů

Abychom si ukázali, jak přidat vlastní token, který je uložený jako součást SaveTokens, ukázková aplikace přidá AuthenticationToken s aktuální DateTime hodnotou pro AuthenticationToken.Name:TicketCreated

services.AddAuthentication().AddGoogle(options =>
{
    // Provide the Google Client ID
    options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"

    // Provide the Google Client Secret
    options.ClientSecret = "{Client Secret}";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"

    options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
    options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
    options.SaveTokens = true;

    options.Events.OnCreatingTicket = ctx =>
    {
        List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList(); 

        tokens.Add(new AuthenticationToken()
        {
            Name = "TicketCreated", 
            Value = DateTime.UtcNow.ToString()
        });

        ctx.Properties.StoreTokens(tokens);

        return Task.CompletedTask;
    };
});

Vytváření a přidávání deklarací identity

Architektura poskytuje běžné akce a metody rozšíření pro vytváření a přidávání deklarací identity do kolekce. Další informace naleznete v tématu ClaimActionCollectionMapExtensions a ClaimActionCollectionUniqueExtensions.

Uživatelé mohou definovat vlastní akce odvozením ClaimAction a implementací abstraktní Run metody.

Další informace najdete na webu Microsoft.AspNetCore.Authentication.OAuth.Claims.

Přidání a aktualizace deklarací identity uživatelů

Deklarace identity se zkopírují z externích zprostředkovatelů do uživatelské databáze při první registraci, ne při přihlášení. Pokud jsou v aplikaci povoleny další deklarace identity poté, co se uživatel zaregistruje k použití aplikace, zavolejte SignInManager.RefreshSignInAsync na uživatele, aby vynutil generování nového ověřování cookie.

Ve vývojovém prostředí pracujícím s testovacími uživatelskými účty můžete jednoduše odstranit a znovu vytvořit uživatelský účet. V produkčních systémech je možné nové deklarace identity přidané do aplikace znovu vyplňovat do uživatelských účtů. Po vygenerování ExternalLogin stránky do aplikace na adrese Areas/Pages/Identity/Account/Manage, přidejte do ExternalLogin.cshtml.csExternalLoginModel souboru následující kód.

Přidejte slovník přidaných deklarací identity. K uložení typů deklarací identity použijte klíče slovníku a hodnoty použijte k uložení výchozí hodnoty. Na začátek třídy přidejte následující řádek. Následující příklad předpokládá, že se pro obrázek Google uživatele přidá jedna deklarace identity s obecným obrázkem headshot jako výchozí hodnotou:

private readonly IReadOnlyDictionary<string, string> _claimsToSync = 
    new Dictionary<string, string>()
    {
        { "urn:google:picture", "https://localhost:5001/headshot.png" },
    };

Nahraďte výchozí kód OnGetCallbackAsync metody následujícím kódem. Kód prochází slovník deklarací identity. Deklarace identity se přidají (zaplní) nebo se aktualizují pro každého uživatele. Při přidání nebo aktualizaci deklarací identity se přihlášení uživatele aktualizuje pomocí SignInManager<TUser>vlastnosti existujícího ověřování (AuthenticationProperties).

public async Task<IActionResult> OnGetCallbackAsync(
    string returnUrl = null, string remoteError = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");

    if (remoteError != null)
    {
        ErrorMessage = $"Error from external provider: {remoteError}";

        return RedirectToPage("./Login", new {ReturnUrl = returnUrl });
    }

    var info = await _signInManager.GetExternalLoginInfoAsync();

    if (info == null)
    {
        ErrorMessage = "Error loading external login information.";
        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }

    // Sign in the user with this external login provider if the user already has a 
    // login.
    var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, 
        info.ProviderKey, isPersistent: false, bypassTwoFactor : true);

    if (result.Succeeded)
    {
        _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", 
            info.Principal.Identity.Name, info.LoginProvider);

        if (_claimsToSync.Count > 0)
        {
            var user = await _userManager.FindByLoginAsync(info.LoginProvider, 
                info.ProviderKey);
            var userClaims = await _userManager.GetClaimsAsync(user);
            bool refreshSignIn = false;

            foreach (var addedClaim in _claimsToSync)
            {
                var userClaim = userClaims
                    .FirstOrDefault(c => c.Type == addedClaim.Key);

                if (info.Principal.HasClaim(c => c.Type == addedClaim.Key))
                {
                    var externalClaim = info.Principal.FindFirst(addedClaim.Key);

                    if (userClaim == null)
                    {
                        await _userManager.AddClaimAsync(user, 
                            new Claim(addedClaim.Key, externalClaim.Value));
                        refreshSignIn = true;
                    }
                    else if (userClaim.Value != externalClaim.Value)
                    {
                        await _userManager
                            .ReplaceClaimAsync(user, userClaim, externalClaim);
                        refreshSignIn = true;
                    }
                }
                else if (userClaim == null)
                {
                    // Fill with a default value
                    await _userManager.AddClaimAsync(user, new Claim(addedClaim.Key, 
                        addedClaim.Value));
                    refreshSignIn = true;
                }
            }

            if (refreshSignIn)
            {
                await _signInManager.RefreshSignInAsync(user);
            }
        }

        return LocalRedirect(returnUrl);
    }

    if (result.IsLockedOut)
    {
        return RedirectToPage("./Lockout");
    }
    else
    {
        // If the user does not have an account, then ask the user to create an 
        // account.
        ReturnUrl = returnUrl;
        ProviderDisplayName = info.ProviderDisplayName;

        if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
        {
            Input = new InputModel
            {
                Email = info.Principal.FindFirstValue(ClaimTypes.Email)
            };
        }

        return Page();
    }
}

Podobný přístup se použije, když se deklarace identity změní, když je uživatel přihlášený, ale krok zpětného vyplňování se nevyžaduje. Chcete-li aktualizovat deklarace identity uživatele, zavolejte na uživatele následující:

Odebrání akcí a deklarací identity

ClaimActionCollection.Remove(String) odebere všechny akce deklarace identity pro danou ClaimType z kolekce. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) odstraní deklaraci dané ClaimType identity. DeleteClaimse primárně používá s OpenID Připojení (OIDC) k odebrání deklarací generovaných protokolem.

Ukázkový výstup aplikace

User Claims

http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
    9b342344f-7aab-43c2-1ac1-ba75912ca999
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
    someone@gmail.com
AspNet.Identity.SecurityStamp
    7D4312MOWRYYBFI1KXRPHGOSTBVWSFDE
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
    Judy
urn:google:locale
    en
urn:google:picture
    https://lh4.googleusercontent.com/-XXXXXX/XXXXXX/XXXXXX/XXXXXX/photo.jpg

Authentication Properties

.Token.access_token
    yc23.AlvoZqz56...1lxltXV7D-ZWP9
.Token.token_type
    Bearer
.Token.expires_at
    2019-04-11T22:14:51.0000000+00:00
.Token.TicketCreated
    4/11/2019 9:14:52 PM
.TokenNames
    access_token;token_type;expires_at;TicketCreated
.persistent
.issued
    Thu, 11 Apr 2019 20:51:06 GMT
.expires
    Thu, 25 Apr 2019 20:51:06 GMT

Předávání informací o požadavcích pomocí proxy serveru nebo nástroje pro vyrovnávání zatížení

Pokud je aplikace nasazená za proxy serverem nebo nástrojem pro vyrovnávání zatížení, mohou se některé z původních informací o požadavku předávat do aplikace v hlavičce požadavku. Tyto informace obvykle zahrnují zabezpečené schéma požadavků (https), hostitele a IP adresu klienta. Aplikace tyto hlavičky požadavků nenačítají automaticky, aby zjistily a využily informace o původním požadavku.

Schéma se používá při generování odkazů, které ovlivňuje tok ověřování s externími zprostředkovateli. Ztráta zabezpečeného schématu (https) způsobí, že aplikace generuje nesprávné a nezabezpečené adresy URL pro přesměrování.

Pomocí middlewaru Forwarded Headers zpřístupníte aplikaci informace o původním požadavku pro zpracování požadavků.

Další informace najdete v tématu Konfigurace ASP.NET Core pro práci s proxy servery a nástroji pro vyrovnávání zatížení.

Aplikace ASP.NET Core může navázat další deklarace identity a tokeny od externích zprostředkovatelů ověřování, jako je Facebook, Google, Microsoft a Twitter. Každý poskytovatel odhalí různé informace o uživatelích na své platformě, ale vzor pro příjem a transformaci uživatelských dat na další deklarace identity je stejný.

Zobrazení nebo stažení ukázkového kódu (postup stažení)

Požadavky

Rozhodněte se, kteří externí zprostředkovatelé ověřování budou v aplikaci podporovat. Pro každého zprostředkovatele zaregistrujte aplikaci a získejte ID klienta a tajný klíč klienta. Další informace najdete v tématu Ověřování Facebookem a Googlem v ASP.NET Core. Ukázková aplikace používá zprostředkovatele ověřování Google.

Nastavení ID klienta a tajného klíče klienta

Zprostředkovatel ověřování OAuth vytvoří vztah důvěryhodnosti s aplikací pomocí ID klienta a tajného klíče klienta. ID klienta a hodnoty tajných kódů klienta se pro aplikaci vytvoří externím zprostředkovatelem ověřování, když je aplikace zaregistrovaná u zprostředkovatele. Každý externí zprostředkovatel, který aplikace používá, musí být nakonfigurován nezávisle na ID klienta a tajném klíči klienta poskytovatele. Další informace najdete v tématech externího zprostředkovatele ověřování, která platí pro váš scénář:

Ukázková aplikace nakonfiguruje zprostředkovatele ověřování Google pomocí ID klienta a tajného klíče klienta poskytovaného Googlem:

services.AddAuthentication().AddGoogle(options =>
{
    // Provide the Google Client ID
    options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"

    // Provide the Google Client Secret
    options.ClientSecret = "{Client Secret}";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"

    options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
    options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
    options.SaveTokens = true;

    options.Events.OnCreatingTicket = ctx =>
    {
        List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList(); 

        tokens.Add(new AuthenticationToken()
        {
            Name = "TicketCreated", 
            Value = DateTime.UtcNow.ToString()
        });

        ctx.Properties.StoreTokens(tokens);

        return Task.CompletedTask;
    };
});

Vytvoření oboru ověřování

Zadejte seznam oprávnění k načtení od zprostředkovatele zadáním .Scope Obory ověřování pro běžné externí zprostředkovatele se zobrazují v následující tabulce.

Poskytovatel Obor
Facebook https://www.facebook.com/dialog/oauth
Google https://www.googleapis.com/auth/userinfo.profile
Microsoft https://login.microsoftonline.com/common/oauth2/v2.0/authorize
Twitter https://api.twitter.com/oauth/authenticate

V ukázkové aplikaci se obor Google userinfo.profile automaticky přidá rozhraním, když AddGoogle je volána na AuthenticationBuilderrozhraní . Pokud aplikace vyžaduje další obory, přidejte je do možností. V následujícím příkladu se přidá obor Google https://www.googleapis.com/auth/user.birthday.read , aby se načetly narozeniny uživatele:

options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");

Mapování uživatelských datových klíčů a vytváření deklarací identity

V možnostech zprostředkovatele zadejte MapJsonKey klíč nebo MapJsonSubKey podklíč v datech uživatele ON externího poskytovatele JSpro identitu aplikace, která se má číst při přihlášení. Další informace o typech deklarací identity najdete v tématu ClaimTypes.

Ukázková aplikace vytvoří deklarace identity národního prostředí (urn:google:locale) a obrázku locale (urn:google:picture) z uživatelských dat Google a picture klíčů:

services.AddAuthentication().AddGoogle(options =>
{
    // Provide the Google Client ID
    options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"

    // Provide the Google Client Secret
    options.ClientSecret = "{Client Secret}";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"

    options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
    options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
    options.SaveTokens = true;

    options.Events.OnCreatingTicket = ctx =>
    {
        List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList(); 

        tokens.Add(new AuthenticationToken()
        {
            Name = "TicketCreated", 
            Value = DateTime.UtcNow.ToString()
        });

        ctx.Properties.StoreTokens(tokens);

        return Task.CompletedTask;
    };
});

V Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsyncaplikaci IdentityUserApplicationUserse přihlásí () pomocí SignInAsyncaplikace . Během procesu UserManager<TUser> přihlášení může ukládat ApplicationUser deklarace identity pro uživatelská data dostupná na webu Principal.

V ukázkové aplikaci OnPostConfirmationAsync vytvoří (Account/ExternalLogin.cshtml.cs) deklarace identity národního prostředí (urn:google:locale) a obrázku (urn:google:picture) pro přihlášené ApplicationUser, včetně deklarace identity pro GivenName:

public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    // Get the information about the user from the external login provider
    var info = await _signInManager.GetExternalLoginInfoAsync();

    if (info == null)
    {
        ErrorMessage = 
            "Error loading external login information during confirmation.";

        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }

    if (ModelState.IsValid)
    {
        var user = new IdentityUser
        {
            UserName = Input.Email, 
            Email = Input.Email 
        };

        var result = await _userManager.CreateAsync(user);

        if (result.Succeeded)
        {
            result = await _userManager.AddLoginAsync(user, info);

            if (result.Succeeded)
            {
                // If they exist, add claims to the user for:
                //    Given (first) name
                //    Locale
                //    Picture
                if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
                {
                    await _userManager.AddClaimAsync(user, 
                        info.Principal.FindFirst(ClaimTypes.GivenName));
                }

                if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
                {
                    await _userManager.AddClaimAsync(user, 
                        info.Principal.FindFirst("urn:google:locale"));
                }

                if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
                {
                    await _userManager.AddClaimAsync(user, 
                        info.Principal.FindFirst("urn:google:picture"));
                }

                // Include the access token in the properties
                var props = new AuthenticationProperties();
                props.StoreTokens(info.AuthenticationTokens);
                props.IsPersistent = true;

                await _signInManager.SignInAsync(user, props);

                _logger.LogInformation(
                    "User created an account using {Name} provider.", 
                    info.LoginProvider);

                return LocalRedirect(returnUrl);
            }
        }

        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    LoginProvider = info.LoginProvider;
    ReturnUrl = returnUrl;
    return Page();
}

Ve výchozím nastavení jsou deklarace identity uživatele uloženy v ověřování cookie. Pokud je ověřování cookie příliš velké, může to způsobit selhání aplikace, protože:

  • Prohlížeč zjistí, že cookie záhlaví je příliš dlouhé.
  • Celková velikost požadavku je příliš velká.

Pokud se ke zpracování žádostí uživatelů vyžaduje velké množství uživatelských dat:

  • Omezte počet a velikost deklarací identity uživatelů pro zpracování požadavků jenom na to, co aplikace vyžaduje.
  • K ukládání identity mezi požadavky použijte vlastní ITicketStoreCookie middlewar SessionStore ověřování. Při odesílání malého klíče identifikátoru relace klientovi zachováte velké množství informací o identitě na serveru.

Uložení přístupového tokenu

SaveTokens definuje, zda mají být po úspěšné autorizaci uloženy AuthenticationProperties přístupové a obnovovací tokeny. SaveTokens je ve výchozím nastavení nastavena tak false , aby se zmenšila velikost konečného ověřování cookie.

Ukázková aplikace nastaví hodnotu SaveTokens in trueGoogleOptions:

services.AddAuthentication().AddGoogle(options =>
{
    // Provide the Google Client ID
    options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"

    // Provide the Google Client Secret
    options.ClientSecret = "{Client Secret}";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"

    options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
    options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
    options.SaveTokens = true;

    options.Events.OnCreatingTicket = ctx =>
    {
        List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList(); 

        tokens.Add(new AuthenticationToken()
        {
            Name = "TicketCreated", 
            Value = DateTime.UtcNow.ToString()
        });

        ctx.Properties.StoreTokens(tokens);

        return Task.CompletedTask;
    };
});

Při OnPostConfirmationAsync spuštění uložte přístupový token (ExternalLoginInfo.AuthenticationTokens) z externího zprostředkovatele do objektu ApplicationUser's AuthenticationProperties.

Ukázková aplikace uloží přístupový token do OnPostConfirmationAsync (registrace nového uživatele) a OnGetCallbackAsync (dříve zaregistrovaného uživatele) do Account/ExternalLogin.cshtml.cs:

public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
    returnUrl = returnUrl ?? Url.Content("~/");
    // Get the information about the user from the external login provider
    var info = await _signInManager.GetExternalLoginInfoAsync();

    if (info == null)
    {
        ErrorMessage = 
            "Error loading external login information during confirmation.";

        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }

    if (ModelState.IsValid)
    {
        var user = new IdentityUser
        {
            UserName = Input.Email, 
            Email = Input.Email 
        };

        var result = await _userManager.CreateAsync(user);

        if (result.Succeeded)
        {
            result = await _userManager.AddLoginAsync(user, info);

            if (result.Succeeded)
            {
                // If they exist, add claims to the user for:
                //    Given (first) name
                //    Locale
                //    Picture
                if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
                {
                    await _userManager.AddClaimAsync(user, 
                        info.Principal.FindFirst(ClaimTypes.GivenName));
                }

                if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
                {
                    await _userManager.AddClaimAsync(user, 
                        info.Principal.FindFirst("urn:google:locale"));
                }

                if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
                {
                    await _userManager.AddClaimAsync(user, 
                        info.Principal.FindFirst("urn:google:picture"));
                }

                // Include the access token in the properties
                var props = new AuthenticationProperties();
                props.StoreTokens(info.AuthenticationTokens);
                props.IsPersistent = true;

                await _signInManager.SignInAsync(user, props);

                _logger.LogInformation(
                    "User created an account using {Name} provider.", 
                    info.LoginProvider);

                return LocalRedirect(returnUrl);
            }
        }

        foreach (var error in result.Errors)
        {
            ModelState.AddModelError(string.Empty, error.Description);
        }
    }

    LoginProvider = info.LoginProvider;
    ReturnUrl = returnUrl;
    return Page();
}

Přidání dalších vlastních tokenů

Abychom si ukázali, jak přidat vlastní token, který je uložený jako součást SaveTokens, ukázková aplikace přidá AuthenticationToken s aktuální DateTime hodnotou pro AuthenticationToken.Name:TicketCreated

services.AddAuthentication().AddGoogle(options =>
{
    // Provide the Google Client ID
    options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"

    // Provide the Google Client Secret
    options.ClientSecret = "{Client Secret}";
    // Register with User Secrets using:
    // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"

    options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
    options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
    options.SaveTokens = true;

    options.Events.OnCreatingTicket = ctx =>
    {
        List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList(); 

        tokens.Add(new AuthenticationToken()
        {
            Name = "TicketCreated", 
            Value = DateTime.UtcNow.ToString()
        });

        ctx.Properties.StoreTokens(tokens);

        return Task.CompletedTask;
    };
});

Vytváření a přidávání deklarací identity

Architektura poskytuje běžné akce a metody rozšíření pro vytváření a přidávání deklarací identity do kolekce. Další informace naleznete v tématu ClaimActionCollectionMapExtensions a ClaimActionCollectionUniqueExtensions.

Uživatelé mohou definovat vlastní akce odvozením ClaimAction a implementací abstraktní Run metody.

Další informace najdete na webu Microsoft.AspNetCore.Authentication.OAuth.Claims.

Odebrání akcí a deklarací identity

ClaimActionCollection.Remove(String) odebere všechny akce deklarace identity pro danou ClaimType z kolekce. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) odstraní deklaraci dané ClaimType identity. DeleteClaimse primárně používá s OpenID Připojení (OIDC) k odebrání deklarací generovaných protokolem.

Ukázkový výstup aplikace

User Claims

http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
    9b342344f-7aab-43c2-1ac1-ba75912ca999
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
    someone@gmail.com
AspNet.Identity.SecurityStamp
    7D4312MOWRYYBFI1KXRPHGOSTBVWSFDE
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
    Judy
urn:google:locale
    en
urn:google:picture
    https://lh4.googleusercontent.com/-XXXXXX/XXXXXX/XXXXXX/XXXXXX/photo.jpg

Authentication Properties

.Token.access_token
    yc23.AlvoZqz56...1lxltXV7D-ZWP9
.Token.token_type
    Bearer
.Token.expires_at
    2019-04-11T22:14:51.0000000+00:00
.Token.TicketCreated
    4/11/2019 9:14:52 PM
.TokenNames
    access_token;token_type;expires_at;TicketCreated
.persistent
.issued
    Thu, 11 Apr 2019 20:51:06 GMT
.expires
    Thu, 25 Apr 2019 20:51:06 GMT

Předávání informací o požadavcích pomocí proxy serveru nebo nástroje pro vyrovnávání zatížení

Pokud je aplikace nasazená za proxy serverem nebo nástrojem pro vyrovnávání zatížení, mohou se některé z původních informací o požadavku předávat do aplikace v hlavičce požadavku. Tyto informace obvykle zahrnují zabezpečené schéma požadavků (https), hostitele a IP adresu klienta. Aplikace tyto hlavičky požadavků nenačítají automaticky, aby zjistily a využily informace o původním požadavku.

Schéma se používá při generování odkazů, které ovlivňuje tok ověřování s externími zprostředkovateli. Ztráta zabezpečeného schématu (https) způsobí, že aplikace generuje nesprávné a nezabezpečené adresy URL pro přesměrování.

Pomocí middlewaru Forwarded Headers zpřístupníte aplikaci informace o původním požadavku pro zpracování požadavků.

Další informace najdete v tématu Konfigurace ASP.NET Core pro práci s proxy servery a nástroji pro vyrovnávání zatížení.

Další prostředky