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í:
- Ověřování pomocí Facebooku
- Ověřování pomocí Googlu
- Ověřování Microsoftu
- Ověřování pomocí Twitteru
- Další zprostředkovatelé ověřování
- OpenIdConnect
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 Entra ID povoluje přiřazování volitelných deklarací 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 |
---|---|
https://www.facebook.com/dialog/oauth |
|
profile , , email openid |
|
Microsoft | https://login.microsoftonline.com/common/oauth2/v2.0/authorize |
https://api.twitter.com/oauth/authenticate |
V ukázkové aplikaci, Google profile
, email
a 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 JSON externího poskytovatele, aby se aplikace identity přečetla 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.OnPostConfirmationAsync
aplikaci IdentityUserApplicationUser
se 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 napříč požadavky použijte vlastní ITicketStore Cookie middleware SessionStore pro ověřování. Při odesílání malého klíče identifikátoru identity relace klientovi zachováte velké množství informací 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 true
GoogleOptions:
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.cs
ExternalLoginModel
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í:
- UserManager.ReplaceClaimAsync pro uživatele pro deklarace identity uložené v identity databázi.
- SignInManager.RefreshSignInAsync pro uživatele, aby vynutil generování nového ověřování cookie.
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 z objektu identity. DeleteClaim se primárně používá s OpenID Connect (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í.
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ář:
- Ověřování pomocí Facebooku
- Ověřování pomocí Googlu
- Ověřování Microsoftu
- Ověřování pomocí Twitteru
- Další zprostředkovatelé ověřování
- OpenIdConnect
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 Entra ID umožňuje přiřadit volitelné deklarace identity k 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:
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 |
---|---|
https://www.facebook.com/dialog/oauth |
|
profile , , email openid |
|
Microsoft | https://login.microsoftonline.com/common/oauth2/v2.0/authorize |
https://api.twitter.com/oauth/authenticate |
V ukázkové aplikaci, Google profile
, email
a 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 uživatelských datech externího zprostředkovatele JSON, aby se aplikace identity přečetla 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.OnPostConfirmationAsync
aplikaci IdentityUserApplicationUser
se 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 napříč požadavky použijte vlastní ITicketStore Cookie middleware SessionStore pro ověřování. Při odesílání malého klíče identifikátoru identity relace klientovi zachováte velké množství informací 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 true
GoogleOptions:
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.cs
ExternalLoginModel
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í:
- UserManager.ReplaceClaimAsync pro uživatele pro deklarace identity uložené v identity databázi.
- SignInManager.RefreshSignInAsync pro uživatele, aby vynutil generování nového ověřování cookie.
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 z objektu identity. DeleteClaim se primárně používá s OpenID Connect (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ář:
- Ověřování pomocí Facebooku
- Ověřování pomocí Googlu
- Ověřování Microsoftu
- Ověřování pomocí Twitteru
- Další zprostředkovatelé ověřování
- OpenIdConnect
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 |
---|---|
https://www.facebook.com/dialog/oauth |
|
https://www.googleapis.com/auth/userinfo.profile |
|
Microsoft | https://login.microsoftonline.com/common/oauth2/v2.0/authorize |
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 poskytovatele zadejte MapJsonKey nebo MapJsonSubKey pro každý klíč nebo podklíč v uživatelských datech externího zprostředkovatele JSON, aby se aplikace identity přečetla 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.OnPostConfirmationAsync
aplikaci IdentityUserApplicationUser
se 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 napříč požadavky použijte vlastní ITicketStore Cookie middleware SessionStore pro ověřování. Při odesílání malého klíče identifikátoru identity relace klientovi zachováte velké množství informací 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 true
GoogleOptions:
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 z objektu identity. DeleteClaim se primárně používá s OpenID Connect (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ší materiály
- dotnet/AspNetCore engineering SocialSample app: Propojená ukázková aplikace je v technické větvi úložiště dotnet/AspNetCore Na GitHubu
main
. Větevmain
obsahuje kód v rámci aktivního vývoje pro příští verzi ASP.NET Core. Pokud chcete zobrazit verzi ukázkové aplikace pro vydanou verzi ASP.NET Core, vyberte větev vydané verze pomocí rozevíracího seznamu Branch (napříkladrelease/{X.Y}
).