Utrwalanie dodatkowych oświadczeń i tokenów od dostawców zewnętrznych w usłudze ASP.NET Core
Aplikacja ASP.NET Core może ustanowić dodatkowe oświadczenia i tokeny od zewnętrznych dostawców uwierzytelniania, takich jak Facebook, Google, Microsoft i Twitter. Każdy dostawca ujawnia różne informacje o użytkownikach na swojej platformie, ale wzorzec odbierania i przekształcania danych użytkownika w dodatkowe oświadczenia jest taki sam.
Wymagania wstępne
Zdecyduj, którzy zewnętrzni dostawcy uwierzytelniania mają obsługiwać aplikację. Dla każdego dostawcy zarejestruj aplikację i uzyskaj identyfikator klienta i klucz tajny klienta. Aby uzyskać więcej informacji, zobacz Uwierzytelnianie w usłudze Facebook i Google w usłudze ASP.NET Core. Przykładowa aplikacja używa dostawcy uwierzytelniania Google.
Ustawianie identyfikatora klienta i klucza tajnego klienta
Dostawca uwierzytelniania OAuth ustanawia relację zaufania z aplikacją przy użyciu identyfikatora klienta i klucza tajnego klienta. Wartości identyfikatora klienta i klucza tajnego klienta są tworzone dla aplikacji przez zewnętrznego dostawcę uwierzytelniania po zarejestrowaniu aplikacji u dostawcy. Każdy dostawca zewnętrzny używany przez aplikację musi być skonfigurowany niezależnie z identyfikatorem klienta dostawcy i kluczem tajnym klienta. Aby uzyskać więcej informacji, zobacz tematy dotyczące zewnętrznego dostawcy uwierzytelniania, które mają zastosowanie:
- Uwierzytelnianie za pomocą konta Facebook
- Uwierzytelnianie za pomocą konta Google
- Uwierzytelnianie firmy Microsoft
- Uwierzytelnianie za pomocą konta Twitter
- Inni dostawcy uwierzytelniania
- OpenIdConnect
Opcjonalne oświadczenia wysyłane w identyfikatorze lub tokenie dostępu od dostawcy uwierzytelniania są zwykle konfigurowane w portalu online dostawcy. Na przykład identyfikator Entra firmy Microsoft zezwala na przypisywanie opcjonalnych oświadczeń do tokenu identyfikatora aplikacji w bloku Konfiguracja tokenu rejestracji aplikacji. Aby uzyskać więcej informacji, zobacz How to: Provide optional claims to your app (Dokumentacja platformy Azure): Provide optional claims to your app (Dokumentacja platformy Azure). W przypadku innych dostawców zapoznaj się z zewnętrznymi zestawami dokumentacji.
Przykładowa aplikacja konfiguruje dostawcę uwierzytelniania Google przy użyciu identyfikatora klienta i klucza tajnego klienta dostarczonego przez firmę Google:
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.
Ustanawianie zakresu uwierzytelniania
Określ listę uprawnień do pobrania z dostawcy, określając element Scope. Zakresy uwierzytelniania dla typowych dostawców zewnętrznych są wyświetlane w poniższej tabeli.
Dostawca | Scope |
---|---|
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 |
W przykładowej aplikacji zakresy , i openid
firmy Google email
profile
są automatycznie dodawane przez platformę po AddGoogle wywołaniu metody AuthenticationBuilder. Jeśli aplikacja wymaga dodatkowych zakresów, dodaj je do opcji. W poniższym przykładzie zakres Google https://www.googleapis.com/auth/user.birthday.read
jest dodawany w celu pobrania urodzin użytkownika:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Mapuj klucze danych użytkownika i twórz oświadczenia
W opcjach dostawcy określ MapJsonKey wartość lub MapJsonSubKey dla każdego klucza lub podklucza w danych użytkownika JSON dostawcy zewnętrznego, aby aplikacja identity odczytywała logowanie. Aby uzyskać więcej informacji na temat typów oświadczeń, zobacz ClaimTypes.
Przykładowa aplikacja tworzy ustawienia regionalne (urn:google:locale
) i obrazy (urn:google:picture
) oświadczenia z locale
kluczy i picture
w danych użytkownika Google:
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;
};
});
W Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync
pliku (IdentityUserApplicationUser
) jest zalogowany do aplikacji przy użyciu SignInAsyncpolecenia . Podczas procesu UserManager<TUser> logowania program może przechowywać ApplicationUser
oświadczenia dotyczące danych użytkownika dostępnych w pliku Principal.
W przykładowej aplikacji OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) ustanawia ustawienia regionalne (urn:google:locale
) i obrazy (urn:google:picture
) oświadczenia dotyczące zalogowanego elementu ApplicationUser
, w tym oświadczenie dla GivenNameelementu :
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();
}
Domyślnie oświadczenia użytkownika są przechowywane w uwierzytelnianiu cookie. Jeśli uwierzytelnianie cookie jest zbyt duże, może to spowodować niepowodzenie aplikacji, ponieważ:
- Przeglądarka wykrywa, że cookie nagłówek jest za długi.
- Ogólny rozmiar żądania jest za duży.
Jeśli do przetwarzania żądań użytkowników jest wymagana duża ilość danych użytkownika:
- Ogranicz liczbę i rozmiar oświadczeń użytkowników na potrzeby przetwarzania żądań tylko do tego, czego wymaga aplikacja.
- Używanie niestandardowego ITicketStore oprogramowania pośredniczącego Cookie SessionStore uwierzytelniania do przechowywania identity między żądaniami. Zachowaj duże ilości identity informacji na serwerze, wysyłając tylko mały klucz identyfikatora sesji do klienta.
Zapisywanie tokenu dostępu
SaveTokens określa, czy tokeny dostępu i odświeżania powinny być przechowywane w AuthenticationProperties obiekcie po pomyślnym uwierzytelnieniu. SaveTokens
parametr jest domyślnie false
ustawiony, aby zmniejszyć rozmiar uwierzytelniania cookiekońcowego.
Przykładowa aplikacja ustawia wartość SaveTokens
na true
w GoogleOptionspliku :
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;
};
});
Podczas OnPostConfirmationAsync
wykonywania zapisz token dostępu (ExternalLoginInfo.AuthenticationTokens) od dostawcy zewnętrznego w pliku ApplicationUser
AuthenticationProperties
.
Przykładowa aplikacja zapisuje token dostępu w programie OnPostConfirmationAsync
(rejestracja nowego użytkownika) i OnGetCallbackAsync
(wcześniej zarejestrowanym użytkowniku) w programie 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();
}
Uwaga
Aby uzyskać informacje na temat przekazywania tokenów do Razor składników aplikacji po stronie Blazor serwera, zobacz ASP.NET Podstawowe scenariusze zabezpieczeń i Blazor Web App dodatkowe scenariusze zabezpieczeń.
Jak dodać dodatkowe tokeny niestandardowe
Aby zademonstrować sposób dodawania tokenu niestandardowego, który jest przechowywany jako część SaveTokens
, przykładowa aplikacja dodaje element AuthenticationToken z bieżącą DateTime wartością dla AuthenticationToken.Name elementu 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;
};
});
Tworzenie i dodawanie oświadczeń
Platforma udostępnia typowe akcje i metody rozszerzenia do tworzenia i dodawania oświadczeń do kolekcji. Aby uzyskać więcej informacji, zobacz i ClaimActionCollectionMapExtensions ClaimActionCollectionUniqueExtensions.
Użytkownicy mogą definiować akcje niestandardowe, wyprowadzając i implementując ClaimAction metodę abstrakcyjną Run .
Aby uzyskać więcej informacji, zobacz Microsoft.AspNetCore.Authentication.OAuth.Claims.
Dodawanie i aktualizowanie oświadczeń użytkowników
Oświadczenia są kopiowane z dostawców zewnętrznych do bazy danych użytkowników podczas pierwszej rejestracji, a nie podczas logowania. Jeśli dodatkowe oświadczenia są włączone w aplikacji po zarejestrowaniu użytkownika w celu korzystania z aplikacji, wywołaj metodę SignInManager.RefreshSignInAsync na użytkowniku, aby wymusić generowanie nowego uwierzytelniania cookie.
W środowisku deweloperów pracującym z kontami użytkowników testowych usuń i ponownie utwórz konto użytkownika. W przypadku systemów produkcyjnych nowe oświadczenia dodane do aplikacji mogą być wypełniane na kontach użytkowników. Po dokonaniu szkieletu ExternalLogin
strony w aplikacji pod adresem Areas/Pages/Identity/Account/Manage
dodaj następujący kod do ExternalLoginModel
pliku w pliku ExternalLogin.cshtml.cs
.
Dodaj słownik dodanych oświadczeń. Użyj kluczy słownika do przechowywania typów oświadczeń i użyj wartości do przechowywania wartości domyślnej. Dodaj następujący wiersz na początku klasy. W poniższym przykładzie przyjęto założenie, że jedno oświadczenie jest dodawane dla obrazu Google użytkownika z ogólnym obrazem headhot jako wartością domyślną:
private readonly IReadOnlyDictionary<string, string> _claimsToSync =
new Dictionary<string, string>()
{
{ "urn:google:picture", "https://localhost:5001/headshot.png" },
};
Zastąp domyślny kod OnGetCallbackAsync
metody następującym kodem. Kod jest zapętlany w słowniku oświadczeń. Oświadczenia są dodawane (wypełnione) lub aktualizowane dla każdego użytkownika. Po dodaniu lub zaktualizowaniu oświadczeń logowanie użytkownika jest odświeżane przy użyciu SignInManager<TUser>elementu , zachowując istniejące właściwości uwierzytelniania (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();
}
}
Podobne podejście jest wykonywane, gdy oświadczenia zmieniają się, gdy użytkownik jest zalogowany, ale krok wypełniania nie jest wymagany. Aby zaktualizować oświadczenia użytkownika, wywołaj następujące polecenie dla użytkownika:
- UserManager.ReplaceClaimAsync dla użytkownika dla oświadczeń przechowywanych w identity bazie danych.
- SignInManager.RefreshSignInAsync na użytkowniku, aby wymusić generowanie nowego uwierzytelniania cookie.
Usuwanie akcji i oświadczeń oświadczeń
ClaimActionCollection.Remove(String) usuwa wszystkie akcje oświadczeń dla danej ClaimType kolekcji. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) usuwa oświadczenie podane ClaimType z obiektu identity. DeleteClaim jest używana głównie z protokołem OpenID Connect (OIDC) w celu usunięcia oświadczeń generowanych przez protokół.
Przykładowe dane wyjściowe aplikacji
Uruchom przykładową aplikację i wybierz link 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
Przekazywanie dalej informacji o żądaniu za pomocą serwera proxy lub modułu równoważenia obciążenia
Jeśli aplikacja jest wdrażana za serwerem proxy lub modułem równoważenia obciążenia, niektóre z pierwotnych informacji o żądaniu mogą zostać przekazane dalej do aplikacji w nagłówkach żądania. Te informacje zazwyczaj obejmują bezpieczny schemat żądań (https
), hosta i adres IP klienta. Aplikacje nie odczytują automatycznie tych nagłówków żądań, aby odnaleźć pierwotne informacje o żądaniu i z nich korzystać.
Schemat jest używany do generowania linków, które mają wpływ na przepływ uwierzytelniania przy użyciu zewnętrznych dostawców. W wyniku utraty bezpiecznego schematu (https
) aplikacja generuje nieprawidłowe niezabezpieczone adresy URL przekierowania.
Użyj oprogramowania pośredniczącego przekazanych nagłówków, aby udostępnić pierwotne informacje o żądaniu do aplikacji w celu przetworzenia żądania.
Aby uzyskać więcej informacji, zobacz Konfigurowanie platformy ASP.NET Core pod kątem pracy z serwerami proxy i modułami równoważenia obciążenia.
Aplikacja ASP.NET Core może ustanowić dodatkowe oświadczenia i tokeny od zewnętrznych dostawców uwierzytelniania, takich jak Facebook, Google, Microsoft i Twitter. Każdy dostawca ujawnia różne informacje o użytkownikach na swojej platformie, ale wzorzec odbierania i przekształcania danych użytkownika w dodatkowe oświadczenia jest taki sam.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Wymagania wstępne
Zdecyduj, którzy zewnętrzni dostawcy uwierzytelniania mają obsługiwać aplikację. Dla każdego dostawcy zarejestruj aplikację i uzyskaj identyfikator klienta i klucz tajny klienta. Aby uzyskać więcej informacji, zobacz Uwierzytelnianie w usłudze Facebook i Google w usłudze ASP.NET Core. Przykładowa aplikacja używa dostawcy uwierzytelniania Google.
Ustawianie identyfikatora klienta i klucza tajnego klienta
Dostawca uwierzytelniania OAuth ustanawia relację zaufania z aplikacją przy użyciu identyfikatora klienta i klucza tajnego klienta. Wartości identyfikatora klienta i klucza tajnego klienta są tworzone dla aplikacji przez zewnętrznego dostawcę uwierzytelniania po zarejestrowaniu aplikacji u dostawcy. Każdy dostawca zewnętrzny używany przez aplikację musi być skonfigurowany niezależnie z identyfikatorem klienta dostawcy i kluczem tajnym klienta. Aby uzyskać więcej informacji, zobacz tematy dotyczące zewnętrznego dostawcy uwierzytelniania, które mają zastosowanie do danego scenariusza:
- Uwierzytelnianie za pomocą konta Facebook
- Uwierzytelnianie za pomocą konta Google
- Uwierzytelnianie firmy Microsoft
- Uwierzytelnianie za pomocą konta Twitter
- Inni dostawcy uwierzytelniania
- OpenIdConnect
Opcjonalne oświadczenia wysyłane w identyfikatorze lub tokenie dostępu od dostawcy uwierzytelniania są zwykle konfigurowane w portalu online dostawcy. Na przykład identyfikator Entra firmy Microsoft umożliwia przypisanie opcjonalnych oświadczeń do tokenu identyfikatora aplikacji w bloku Konfiguracja tokenu rejestracji aplikacji. Aby uzyskać więcej informacji, zobacz How to: Provide optional claims to your app (Dokumentacja platformy Azure): Provide optional claims to your app (Dokumentacja platformy Azure). W przypadku innych dostawców zapoznaj się z zewnętrznymi zestawami dokumentacji.
Przykładowa aplikacja konfiguruje dostawcę uwierzytelniania Google przy użyciu identyfikatora klienta i klucza tajnego klienta dostarczonego przez firmę Google:
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;
};
});
Ustanawianie zakresu uwierzytelniania
Określ listę uprawnień do pobrania z dostawcy, określając element Scope. Zakresy uwierzytelniania dla typowych dostawców zewnętrznych są wyświetlane w poniższej tabeli.
Dostawca | Scope |
---|---|
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 |
W przykładowej aplikacji zakresy , i openid
firmy Google email
profile
są automatycznie dodawane przez platformę po AddGoogle wywołaniu metody AuthenticationBuilder. Jeśli aplikacja wymaga dodatkowych zakresów, dodaj je do opcji. W poniższym przykładzie zakres Google https://www.googleapis.com/auth/user.birthday.read
jest dodawany w celu pobrania urodzin użytkownika:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Mapuj klucze danych użytkownika i twórz oświadczenia
W opcjach dostawcy określ MapJsonKey wartość lub MapJsonSubKey dla każdego klucza/podklucza w danych użytkownika JSON dostawcy zewnętrznego, aby aplikacja identity odczytywała logowanie. Aby uzyskać więcej informacji na temat typów oświadczeń, zobacz ClaimTypes.
Przykładowa aplikacja tworzy ustawienia regionalne (urn:google:locale
) i obrazy (urn:google:picture
) oświadczenia z locale
kluczy i picture
w danych użytkownika Google:
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;
};
});
W Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync
pliku (IdentityUserApplicationUser
) jest zalogowany do aplikacji przy użyciu SignInAsyncpolecenia . Podczas procesu UserManager<TUser> logowania program może przechowywać ApplicationUser
oświadczenia dotyczące danych użytkownika dostępnych w pliku Principal.
W przykładowej aplikacji OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) ustanawia ustawienia regionalne (urn:google:locale
) i obrazy (urn:google:picture
) oświadczenia dotyczące zalogowanego elementu ApplicationUser
, w tym oświadczenie dla GivenNameelementu :
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();
}
Domyślnie oświadczenia użytkownika są przechowywane w uwierzytelnianiu cookie. Jeśli uwierzytelnianie cookie jest zbyt duże, może to spowodować niepowodzenie aplikacji, ponieważ:
- Przeglądarka wykrywa, że cookie nagłówek jest za długi.
- Ogólny rozmiar żądania jest za duży.
Jeśli do przetwarzania żądań użytkowników jest wymagana duża ilość danych użytkownika:
- Ogranicz liczbę i rozmiar oświadczeń użytkowników na potrzeby przetwarzania żądań tylko do tego, czego wymaga aplikacja.
- Używanie niestandardowego ITicketStore oprogramowania pośredniczącego Cookie SessionStore uwierzytelniania do przechowywania identity między żądaniami. Zachowaj duże ilości identity informacji na serwerze, wysyłając tylko mały klucz identyfikatora sesji do klienta.
Zapisywanie tokenu dostępu
SaveTokens określa, czy tokeny dostępu i odświeżania powinny być przechowywane w AuthenticationProperties obiekcie po pomyślnym uwierzytelnieniu. SaveTokens
parametr jest domyślnie false
ustawiony, aby zmniejszyć rozmiar uwierzytelniania cookiekońcowego.
Przykładowa aplikacja ustawia wartość SaveTokens
na true
w GoogleOptionspliku :
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;
};
});
Podczas OnPostConfirmationAsync
wykonywania zapisz token dostępu (ExternalLoginInfo.AuthenticationTokens) od dostawcy zewnętrznego w pliku ApplicationUser
AuthenticationProperties
.
Przykładowa aplikacja zapisuje token dostępu w programie OnPostConfirmationAsync
(rejestracja nowego użytkownika) i OnGetCallbackAsync
(wcześniej zarejestrowanym użytkowniku) w programie 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();
}
Uwaga
Aby uzyskać informacje na temat przekazywania tokenów do Razor składników aplikacji po stronie Blazor serwera, zobacz ASP.NET Podstawowe scenariusze zabezpieczeń i Blazor Web App dodatkowe scenariusze zabezpieczeń.
Jak dodać dodatkowe tokeny niestandardowe
Aby zademonstrować sposób dodawania tokenu niestandardowego, który jest przechowywany jako część SaveTokens
, przykładowa aplikacja dodaje element AuthenticationToken z bieżącą DateTime wartością dla AuthenticationToken.Name elementu 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;
};
});
Tworzenie i dodawanie oświadczeń
Platforma udostępnia typowe akcje i metody rozszerzenia do tworzenia i dodawania oświadczeń do kolekcji. Aby uzyskać więcej informacji, zobacz i ClaimActionCollectionMapExtensions ClaimActionCollectionUniqueExtensions.
Użytkownicy mogą definiować akcje niestandardowe, wyprowadzając i implementując ClaimAction metodę abstrakcyjną Run .
Aby uzyskać więcej informacji, zobacz Microsoft.AspNetCore.Authentication.OAuth.Claims.
Dodawanie i aktualizowanie oświadczeń użytkowników
Oświadczenia są kopiowane z dostawców zewnętrznych do bazy danych użytkowników podczas pierwszej rejestracji, a nie podczas logowania. Jeśli dodatkowe oświadczenia są włączone w aplikacji po zarejestrowaniu użytkownika w celu korzystania z aplikacji, wywołaj metodę SignInManager.RefreshSignInAsync na użytkowniku, aby wymusić generowanie nowego uwierzytelniania cookie.
W środowisku deweloperów pracującym z kontami użytkowników testowych możesz po prostu usunąć i ponownie utworzyć konto użytkownika. W przypadku systemów produkcyjnych nowe oświadczenia dodane do aplikacji mogą być wypełniane na kontach użytkowników. Po dokonaniu szkieletu ExternalLogin
strony w aplikacji pod adresem Areas/Pages/Identity/Account/Manage
dodaj następujący kod do ExternalLoginModel
pliku w pliku ExternalLogin.cshtml.cs
.
Dodaj słownik dodanych oświadczeń. Użyj kluczy słownika do przechowywania typów oświadczeń i użyj wartości do przechowywania wartości domyślnej. Dodaj następujący wiersz na początku klasy. W poniższym przykładzie przyjęto założenie, że jedno oświadczenie jest dodawane dla obrazu Google użytkownika z ogólnym obrazem headhot jako wartością domyślną:
private readonly IReadOnlyDictionary<string, string> _claimsToSync =
new Dictionary<string, string>()
{
{ "urn:google:picture", "https://localhost:5001/headshot.png" },
};
Zastąp domyślny kod OnGetCallbackAsync
metody następującym kodem. Kod jest zapętlany w słowniku oświadczeń. Oświadczenia są dodawane (wypełnione) lub aktualizowane dla każdego użytkownika. Po dodaniu lub zaktualizowaniu oświadczeń logowanie użytkownika jest odświeżane przy użyciu SignInManager<TUser>elementu , zachowując istniejące właściwości uwierzytelniania (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();
}
}
Podobne podejście jest wykonywane, gdy oświadczenia zmieniają się, gdy użytkownik jest zalogowany, ale krok wypełniania nie jest wymagany. Aby zaktualizować oświadczenia użytkownika, wywołaj następujące polecenie dla użytkownika:
- UserManager.ReplaceClaimAsync dla użytkownika dla oświadczeń przechowywanych w identity bazie danych.
- SignInManager.RefreshSignInAsync na użytkowniku, aby wymusić generowanie nowego uwierzytelniania cookie.
Usuwanie akcji i oświadczeń oświadczeń
ClaimActionCollection.Remove(String) usuwa wszystkie akcje oświadczeń dla danej ClaimType kolekcji. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) usuwa oświadczenie podane ClaimType z obiektu identity. DeleteClaim jest używana głównie z protokołem OpenID Connect (OIDC) w celu usunięcia oświadczeń generowanych przez protokół.
Przykładowe dane wyjściowe aplikacji
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
Przekazywanie dalej informacji o żądaniu za pomocą serwera proxy lub modułu równoważenia obciążenia
Jeśli aplikacja jest wdrażana za serwerem proxy lub modułem równoważenia obciążenia, niektóre z pierwotnych informacji o żądaniu mogą zostać przekazane dalej do aplikacji w nagłówkach żądania. Te informacje zazwyczaj obejmują bezpieczny schemat żądań (https
), hosta i adres IP klienta. Aplikacje nie odczytują automatycznie tych nagłówków żądań, aby odnaleźć pierwotne informacje o żądaniu i z nich korzystać.
Schemat jest używany do generowania linków, które mają wpływ na przepływ uwierzytelniania przy użyciu zewnętrznych dostawców. W wyniku utraty bezpiecznego schematu (https
) aplikacja generuje nieprawidłowe niezabezpieczone adresy URL przekierowania.
Użyj oprogramowania pośredniczącego przekazanych nagłówków, aby udostępnić pierwotne informacje o żądaniu do aplikacji w celu przetworzenia żądania.
Aby uzyskać więcej informacji, zobacz Konfigurowanie platformy ASP.NET Core pod kątem pracy z serwerami proxy i modułami równoważenia obciążenia.
Aplikacja ASP.NET Core może ustanowić dodatkowe oświadczenia i tokeny od zewnętrznych dostawców uwierzytelniania, takich jak Facebook, Google, Microsoft i Twitter. Każdy dostawca ujawnia różne informacje o użytkownikach na swojej platformie, ale wzorzec odbierania i przekształcania danych użytkownika w dodatkowe oświadczenia jest taki sam.
Wyświetl lub pobierz przykładowy kod (jak pobrać)
Wymagania wstępne
Zdecyduj, którzy zewnętrzni dostawcy uwierzytelniania mają obsługiwać aplikację. Dla każdego dostawcy zarejestruj aplikację i uzyskaj identyfikator klienta i klucz tajny klienta. Aby uzyskać więcej informacji, zobacz Uwierzytelnianie w usłudze Facebook i Google w usłudze ASP.NET Core. Przykładowa aplikacja używa dostawcy uwierzytelniania Google.
Ustawianie identyfikatora klienta i klucza tajnego klienta
Dostawca uwierzytelniania OAuth ustanawia relację zaufania z aplikacją przy użyciu identyfikatora klienta i klucza tajnego klienta. Wartości identyfikatora klienta i klucza tajnego klienta są tworzone dla aplikacji przez zewnętrznego dostawcę uwierzytelniania po zarejestrowaniu aplikacji u dostawcy. Każdy dostawca zewnętrzny używany przez aplikację musi być skonfigurowany niezależnie z identyfikatorem klienta dostawcy i kluczem tajnym klienta. Aby uzyskać więcej informacji, zobacz tematy dotyczące zewnętrznego dostawcy uwierzytelniania, które mają zastosowanie do danego scenariusza:
- Uwierzytelnianie za pomocą konta Facebook
- Uwierzytelnianie za pomocą konta Google
- Uwierzytelnianie firmy Microsoft
- Uwierzytelnianie za pomocą konta Twitter
- Inni dostawcy uwierzytelniania
- OpenIdConnect
Przykładowa aplikacja konfiguruje dostawcę uwierzytelniania Google przy użyciu identyfikatora klienta i klucza tajnego klienta dostarczonego przez firmę Google:
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;
};
});
Ustanawianie zakresu uwierzytelniania
Określ listę uprawnień do pobrania z dostawcy, określając element Scope. Zakresy uwierzytelniania dla typowych dostawców zewnętrznych są wyświetlane w poniższej tabeli.
Dostawca | Scope |
---|---|
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 |
W przykładowej aplikacji zakres google userinfo.profile
jest automatycznie dodawany przez platformę, gdy AddGoogle jest wywoływany w obiekcie AuthenticationBuilder. Jeśli aplikacja wymaga dodatkowych zakresów, dodaj je do opcji. W poniższym przykładzie zakres Google https://www.googleapis.com/auth/user.birthday.read
jest dodawany w celu pobrania urodzin użytkownika:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Mapuj klucze danych użytkownika i twórz oświadczenia
W opcjach dostawcy określ MapJsonKey wartość lub MapJsonSubKey dla każdego klucza/podklucza w danych użytkownika JSON dostawcy zewnętrznego, aby aplikacja identity odczytywała logowanie. Aby uzyskać więcej informacji na temat typów oświadczeń, zobacz ClaimTypes.
Przykładowa aplikacja tworzy ustawienia regionalne (urn:google:locale
) i obrazy (urn:google:picture
) oświadczenia z locale
kluczy i picture
w danych użytkownika Google:
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;
};
});
W Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync
pliku (IdentityUserApplicationUser
) jest zalogowany do aplikacji przy użyciu SignInAsyncpolecenia . Podczas procesu UserManager<TUser> logowania program może przechowywać ApplicationUser
oświadczenia dotyczące danych użytkownika dostępnych w pliku Principal.
W przykładowej aplikacji OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) ustanawia ustawienia regionalne (urn:google:locale
) i obrazy (urn:google:picture
) oświadczenia dotyczące zalogowanego elementu ApplicationUser
, w tym oświadczenie dla GivenNameelementu :
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();
}
Domyślnie oświadczenia użytkownika są przechowywane w uwierzytelnianiu cookie. Jeśli uwierzytelnianie cookie jest zbyt duże, może to spowodować niepowodzenie aplikacji, ponieważ:
- Przeglądarka wykrywa, że cookie nagłówek jest za długi.
- Ogólny rozmiar żądania jest za duży.
Jeśli do przetwarzania żądań użytkowników jest wymagana duża ilość danych użytkownika:
- Ogranicz liczbę i rozmiar oświadczeń użytkowników na potrzeby przetwarzania żądań tylko do tego, czego wymaga aplikacja.
- Używanie niestandardowego ITicketStore oprogramowania pośredniczącego Cookie SessionStore uwierzytelniania do przechowywania identity między żądaniami. Zachowaj duże ilości identity informacji na serwerze, wysyłając tylko mały klucz identyfikatora sesji do klienta.
Zapisywanie tokenu dostępu
SaveTokens określa, czy tokeny dostępu i odświeżania powinny być przechowywane w AuthenticationProperties obiekcie po pomyślnym uwierzytelnieniu. SaveTokens
parametr jest domyślnie false
ustawiony, aby zmniejszyć rozmiar uwierzytelniania cookiekońcowego.
Przykładowa aplikacja ustawia wartość SaveTokens
na true
w GoogleOptionspliku :
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;
};
});
Podczas OnPostConfirmationAsync
wykonywania zapisz token dostępu (ExternalLoginInfo.AuthenticationTokens) od dostawcy zewnętrznego w pliku ApplicationUser
AuthenticationProperties
.
Przykładowa aplikacja zapisuje token dostępu w programie OnPostConfirmationAsync
(rejestracja nowego użytkownika) i OnGetCallbackAsync
(wcześniej zarejestrowanym użytkowniku) w programie 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();
}
Jak dodać dodatkowe tokeny niestandardowe
Aby zademonstrować sposób dodawania tokenu niestandardowego, który jest przechowywany jako część SaveTokens
, przykładowa aplikacja dodaje element AuthenticationToken z bieżącą DateTime wartością dla AuthenticationToken.Name elementu 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;
};
});
Tworzenie i dodawanie oświadczeń
Platforma udostępnia typowe akcje i metody rozszerzenia do tworzenia i dodawania oświadczeń do kolekcji. Aby uzyskać więcej informacji, zobacz i ClaimActionCollectionMapExtensions ClaimActionCollectionUniqueExtensions.
Użytkownicy mogą definiować akcje niestandardowe, wyprowadzając i implementując ClaimAction metodę abstrakcyjną Run .
Aby uzyskać więcej informacji, zobacz Microsoft.AspNetCore.Authentication.OAuth.Claims.
Usuwanie akcji i oświadczeń oświadczeń
ClaimActionCollection.Remove(String) usuwa wszystkie akcje oświadczeń dla danej ClaimType kolekcji. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) usuwa oświadczenie podane ClaimType z obiektu identity. DeleteClaim jest używana głównie z protokołem OpenID Connect (OIDC) w celu usunięcia oświadczeń generowanych przez protokół.
Przykładowe dane wyjściowe aplikacji
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
Przekazywanie dalej informacji o żądaniu za pomocą serwera proxy lub modułu równoważenia obciążenia
Jeśli aplikacja jest wdrażana za serwerem proxy lub modułem równoważenia obciążenia, niektóre z pierwotnych informacji o żądaniu mogą zostać przekazane dalej do aplikacji w nagłówkach żądania. Te informacje zazwyczaj obejmują bezpieczny schemat żądań (https
), hosta i adres IP klienta. Aplikacje nie odczytują automatycznie tych nagłówków żądań, aby odnaleźć pierwotne informacje o żądaniu i z nich korzystać.
Schemat jest używany do generowania linków, które mają wpływ na przepływ uwierzytelniania przy użyciu zewnętrznych dostawców. W wyniku utraty bezpiecznego schematu (https
) aplikacja generuje nieprawidłowe niezabezpieczone adresy URL przekierowania.
Użyj oprogramowania pośredniczącego przekazanych nagłówków, aby udostępnić pierwotne informacje o żądaniu do aplikacji w celu przetworzenia żądania.
Aby uzyskać więcej informacji, zobacz Konfigurowanie platformy ASP.NET Core pod kątem pracy z serwerami proxy i modułami równoważenia obciążenia.
Dodatkowe zasoby
- dotnet/AspNetCore engineering SocialSample app: połączona przykładowa aplikacja znajduje się w repozytorium
main
github dotnet/AspNetCore. Gałąźmain
zawiera kod w ramach aktywnego programowania dla następnej wersji ASP.NET Core. Aby wyświetlić wersję przykładowej aplikacji dla wydanej wersji ASP.NET Core, użyj listy rozwijanej Gałąź , aby wybrać gałąź wydania (na przykładrelease/{X.Y}
).