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 profile
firmy Google email
openid
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 lub MapJsonSubKey dla każdego klucza lub podklucza w danych użytkownika JSON dostawcy zewnętrznego, aby aplikacja mogła odczytać podczas logowania. 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żyj niestandardowego ITicketStore dla SessionStore oprogramowania pośredniczącego uwierzytelniania Cookie do przechowywania tożsamości między żądaniami. Zachowaj duże ilości informacji o tożsamości 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 ClaimActionCollectionMapExtensionsClaimActionCollectionUniqueExtensions.
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 w przypadku roszczeń przechowywanych w bazie danych tożsamości.
- 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 roszczenie danego ClaimType z tożsamości. 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 profile
firmy Google email
openid
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 lub MapJsonSubKey dla każdego klucza/podklucza w danych użytkownika JSON dostawcy zewnętrznego, aby tożsamość aplikacji mogła odczytać te dane podczas logowania. 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żyj niestandardowego ITicketStore dla SessionStore oprogramowania pośredniczącego uwierzytelniania Cookie do przechowywania tożsamości między żądaniami. Zachowaj duże ilości informacji o tożsamości 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 ClaimActionCollectionMapExtensionsClaimActionCollectionUniqueExtensions.
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 bazie danych tożsamości.
- 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 roszczenie danego ClaimType z tożsamości. 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 lub MapJsonSubKey dla każdego klucza/podklucza w danych użytkownika JSON dostawcy zewnętrznego, aby można było odczytać tożsamość aplikacji przy logowaniu. 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żyj niestandardowego ITicketStore w oprogramowaniu pośredniczącym uwierzytelniania Cookie, aby przechowywać tożsamość za pomocą SessionStore między żądaniami. Zachowaj duże ilości informacji o tożsamości 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 ClaimActionCollectionMapExtensionsClaimActionCollectionUniqueExtensions.
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 roszczenie owego ClaimType z tożsamości. 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}
).