Сохранение дополнительных утверждений и маркеров от внешних поставщиков в ASP.NET Core
Приложение ASP.NET Core может устанавливать дополнительные утверждения и маркеры от внешних поставщиков проверки подлинности, таких как Facebook, Google, Microsoft и Twitter. Каждый поставщик показывает различные сведения о пользователях на своей платформе, но шаблон получения и преобразования данных пользователей в дополнительные утверждения совпадает.
Необходимые компоненты
Определите, какие внешние поставщики проверки подлинности будут поддерживать в приложении. Для каждого поставщика зарегистрируйте приложение и получите идентификатор клиента и секрет клиента. Дополнительные сведения см. в статье "Проверка подлинности Facebook и Google" в ASP.NET Core. В примере приложения используется поставщик проверки подлинности Google.
Установка идентификатора клиента и секрета клиента
Поставщик проверки подлинности OAuth устанавливает отношение доверия с приложением с помощью идентификатора клиента и секрета клиента. Идентификатор клиента и значения секрета клиента создаются внешним поставщиком проверки подлинности при регистрации приложения в поставщике. Каждый внешний поставщик, который использует приложение, должен быть настроен независимо с идентификатором клиента поставщика и секретом клиента. Дополнительные сведения см. в разделах внешнего поставщика проверки подлинности, которые применяются:
- Проверка подлинности Facebook
- Проверка подлинности Google
- Проверка подлинности Майкрософт
- Проверка подлинности Twitter
- Другие поставщики проверки подлинности
- OpenIdConnect
Необязательные утверждения, отправленные в идентификатор или маркер доступа от поставщика проверки подлинности, обычно настраиваются на веб-портале поставщика. Например, идентификатор Microsoft Entra позволяет назначать необязательные утверждения маркеру идентификатора приложения в колонке конфигурации маркера регистрации приложения. Дополнительные сведения см. в статье "Практическое руководство. Предоставление дополнительных утверждений приложению (документация Azure)". Для других поставщиков обратитесь к своим внешним наборам документации.
Пример приложения настраивает поставщика проверки подлинности Google с идентификатором клиента и секретом клиента, предоставленным 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.
Установка области проверки подлинности
Укажите список разрешений для получения от поставщика, указав параметр Scope. Области проверки подлинности для распространенных внешних поставщиков отображаются в следующей таблице.
Provider | Область |
---|---|
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 |
В примере приложения Google profile
email
и openid
области автоматически добавляются платформой при AddGoogle вызовеAuthenticationBuilder. Если приложению требуются дополнительные области, добавьте их в параметры. В следующем примере область Google https://www.googleapis.com/auth/user.birthday.read
добавляется для получения дня рождения пользователя:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Сопоставление ключей данных пользователя и создание утверждений
В параметрах поставщика укажите MapJsonKey MapJsonSubKey или для каждого ключа или подраздела в данных пользователя JSON внешнего поставщика, чтобы приложение identity читалось при входе. Дополнительные сведения о типах утверждений см. в разделе ClaimTypes.
Пример приложения создает утверждения языкового стандарта () и рисунка locale
(urn:google:locale
urn:google:picture
) из данных пользователя Google и picture
ключей:
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;
};
});
В Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync
, а IdentityUser (ApplicationUser
) войдет в приложение с SignInAsyncпомощью . Во время входа в систему UserManager<TUser> можно хранить ApplicationUser
утверждения для пользовательских данных, доступных на сайте Principal.
В примере приложения OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) устанавливает утверждения языкового стандарта (urn:google:locale
) и рисунка (urn:google:picture
) для входа ApplicationUser
, включая утверждение для GivenName:
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = CreateUser();
await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
// If they exist, add claims to the user for:
// Given (first) name
// Locale
// Picture
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst(ClaimTypes.GivenName));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:locale"));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:picture"));
}
// Include the access token in the properties
// using Microsoft.AspNetCore.Authentication;
var props = new AuthenticationProperties();
props.StoreTokens(info.AuthenticationTokens);
props.IsPersistent = false;
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
// If account confirmation is required, we need to show the link if we don't have a real email sender
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
}
await _signInManager.SignInAsync(user, props, info.LoginProvider);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
ProviderDisplayName = info.ProviderDisplayName;
ReturnUrl = returnUrl;
return Page();
}
По умолчанию утверждения пользователя хранятся в проверке подлинности cookie. Если проверка подлинности cookie слишком велика, это может привести к сбою приложения, так как:
- Браузер обнаруживает, что cookie заголовок слишком длинный.
- Общий размер запроса слишком велик.
Если для обработки запросов пользователей требуется большое количество данных пользователя:
- Ограничьте количество и размер утверждений пользователей для обработки запросов только тем, что требуется приложению.
- Используйте настраиваемый ITicketStore для по промежуточного Cookie слоя SessionStore проверки подлинности для хранения identity между запросами. Сохраняйте большие объемы identity информации на сервере, отправляя клиенту только небольшой ключ идентификатора сеанса.
Сохранение маркера доступа
SaveTokens определяет, следует ли хранить маркеры доступа и обновления в AuthenticationProperties после успешной авторизации. SaveTokens
По умолчанию устанавливается значение false
, чтобы уменьшить размер окончательной проверки подлинности cookie.
Пример приложения задает значение SaveTokens
true
в GoogleOptions:
builder.Services.AddAuthentication().AddGoogle(googleOptions =>
{
googleOptions.ClientId = configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = configuration["Authentication:Google:ClientSecret"];
googleOptions.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
googleOptions.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
googleOptions.SaveTokens = true;
googleOptions.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
При OnPostConfirmationAsync
выполнении сохраните маркер доступа (ExternalLoginInfo.AuthenticationTokens) из внешнего поставщика в ApplicationUser
's AuthenticationProperties
.
Пример приложения сохраняет маркер доступа в OnPostConfirmationAsync
(новая регистрация пользователя) и OnGetCallbackAsync
(ранее зарегистрированный пользователь) в 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();
}
Примечание.
Сведения о передаче маркеров Razor компонентам серверного приложения см. в дополнительных сценариях безопасности на стороне Blazor сервера ASP.NET CoreBlazor.
Добавление дополнительных пользовательских маркеров
Чтобы продемонстрировать, как добавить пользовательский маркер, который хранится в составеSaveTokens
, пример приложения добавляет текущее DateTime AuthenticationToken значение для AuthenticationToken.NameTicketCreated
:
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;
};
});
Создание и добавление утверждений
Платформа предоставляет общие действия и методы расширения для создания и добавления утверждений в коллекцию. Дополнительные сведения см. в разделах ClaimActionCollectionMapExtensions и ClaimActionCollectionUniqueExtensions.
Пользователи могут определять пользовательские действия, исходя из ClaimAction абстрактного Run метода и реализуя его.
Дополнительные сведения см. в разделе Microsoft.AspNetCore.Authentication.OAuth.Claims.
Добавление и обновление утверждений пользователей
Утверждения копируются из внешних поставщиков в пользовательную базу данных при первой регистрации, а не при входе. Если в приложении включены дополнительные утверждения после регистрации пользователя для использования приложения, вызовите SignInManager.RefreshSignInAsync для пользователя принудительное создание новой проверки подлинности cookie.
В среде разработки, работающей с тестовыми учетными записями пользователей, удалите и повторно создайте учетную запись пользователя. Для рабочих систем новые утверждения, добавленные в приложение, можно заполнить в учетные записи пользователей. После создания шаблонов страницы ExternalLogin
в приложение Areas/Pages/Identity/Account/Manage
добавьте следующий код ExternalLoginModel
в ExternalLogin.cshtml.cs
файл.
Добавьте словарь добавленных утверждений. Используйте ключи словаря для хранения типов утверждений и использования значений для хранения значения по умолчанию. Добавьте следующую строку в начало класса. В следующем примере предполагается, что одно утверждение добавляется для изображения Google пользователя с универсальным изображением headshot в качестве значения по умолчанию:
private readonly IReadOnlyDictionary<string, string> _claimsToSync =
new Dictionary<string, string>()
{
{ "urn:google:picture", "https://localhost:5001/headshot.png" },
};
Замените код OnGetCallbackAsync
метода по умолчанию следующим кодом. Код циклит по словарю утверждений. Утверждения добавляются (резервная копия) или обновляются для каждого пользователя. При добавлении или обновлении утверждений вход пользователя обновляется с помощью SignInManager<TUser>существующего свойства проверки подлинности (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();
}
}
Аналогичный подход применяется при изменении утверждений во время входа пользователя, но шаг обратной заполнения не требуется. Чтобы обновить утверждения пользователя, вызовите следующее для пользователя:
- UserManager.ReplaceClaimAsync для пользователей утверждений identity , хранящихся в базе данных.
- SignInManager.RefreshSignInAsync для пользователя, чтобы принудительно создать новую проверку подлинности cookie.
Удаление действий и утверждений утверждений
ClaimActionCollection.Remove(String) удаляет все действия утверждения для заданной ClaimType коллекции. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) удаляет утверждение заданного ClaimType объекта.identity DeleteClaim в основном используется с OpenID Connect (OIDC) для удаления утверждений, созданных протоколом.
Пример выходных данных приложения
Запустите пример приложения и выберите ссылку 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
Переадресация сведений запроса с помощью прокси-сервера или подсистемы балансировки нагрузки
Если приложение развертывается с прокси-сервером или подсистемой балансировки нагрузки, некоторые сведения из исходного запроса можно перенаправить в приложение в заголовках запроса. Эти сведения обычно включают безопасную схему запроса (https
), узел и IP-адрес клиента. Приложения не считывают автоматически и не используют эти заголовки запроса.
Схема используется для создания ссылок, определяющих процесс проверки подлинности с помощью внешних поставщиков. Потеря безопасной схемы (https
) приводит к тому, что приложение неправильно выполняет перенаправление на небезопасные URL-адреса.
Используйте ПО промежуточного слоя для перенаправленных заголовков, чтобы предоставить приложению сведения из исходных запросов для обработки запросов.
Дополнительные сведения см. в разделе Настройка ASP.NET Core для работы с прокси-серверами и подсистемами балансировки нагрузки.
Приложение ASP.NET Core может устанавливать дополнительные утверждения и маркеры от внешних поставщиков проверки подлинности, таких как Facebook, Google, Microsoft и Twitter. Каждый поставщик показывает различные сведения о пользователях на своей платформе, но шаблон получения и преобразования данных пользователей в дополнительные утверждения совпадает.
Просмотреть или скачать образец кода (описание загрузки)
Необходимые компоненты
Определите, какие внешние поставщики проверки подлинности будут поддерживать в приложении. Для каждого поставщика зарегистрируйте приложение и получите идентификатор клиента и секрет клиента. Дополнительные сведения см. в статье "Проверка подлинности Facebook и Google" в ASP.NET Core. В примере приложения используется поставщик проверки подлинности Google.
Установка идентификатора клиента и секрета клиента
Поставщик проверки подлинности OAuth устанавливает отношение доверия с приложением с помощью идентификатора клиента и секрета клиента. Идентификатор клиента и значения секрета клиента создаются внешним поставщиком проверки подлинности при регистрации приложения в поставщике. Каждый внешний поставщик, который использует приложение, должен быть настроен независимо с идентификатором клиента поставщика и секретом клиента. Дополнительные сведения см. в разделах внешнего поставщика проверки подлинности, которые применяются к вашему сценарию:
- Проверка подлинности Facebook
- Проверка подлинности Google
- Проверка подлинности Майкрософт
- Проверка подлинности Twitter
- Другие поставщики проверки подлинности
- OpenIdConnect
Необязательные утверждения, отправленные в идентификатор или маркер доступа от поставщика проверки подлинности, обычно настраиваются на веб-портале поставщика. Например, идентификатор Microsoft Entra позволяет назначать необязательные утверждения маркеру идентификатора приложения в колонке конфигурации маркера регистрации приложения. Дополнительные сведения см. в статье "Практическое руководство. Предоставление дополнительных утверждений приложению (документация Azure)". Для других поставщиков обратитесь к своим внешним наборам документации.
Пример приложения настраивает поставщика проверки подлинности Google с идентификатором клиента и секретом клиента, предоставленным 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;
};
});
Установка области проверки подлинности
Укажите список разрешений для получения от поставщика, указав параметр Scope. Области проверки подлинности для распространенных внешних поставщиков отображаются в следующей таблице.
Provider | Область |
---|---|
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 |
В примере приложения Google profile
email
и openid
области автоматически добавляются платформой при AddGoogle вызовеAuthenticationBuilder. Если приложению требуются дополнительные области, добавьте их в параметры. В следующем примере область Google https://www.googleapis.com/auth/user.birthday.read
добавляется для получения дня рождения пользователя:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Сопоставление ключей данных пользователя и создание утверждений
В параметрах поставщика укажите MapJsonKey MapJsonSubKey или для каждого ключа или подраздела в пользовательских данных JSON внешнего поставщика, чтобы приложение identity читалось при входе. Дополнительные сведения о типах утверждений см. в разделе ClaimTypes.
Пример приложения создает утверждения языкового стандарта () и рисунка locale
(urn:google:locale
urn:google:picture
) из данных пользователя Google и picture
ключей:
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;
};
});
В Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync
, а IdentityUser (ApplicationUser
) войдет в приложение с SignInAsyncпомощью . Во время входа в систему UserManager<TUser> можно хранить ApplicationUser
утверждения для пользовательских данных, доступных на сайте Principal.
В примере приложения OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) устанавливает утверждения языкового стандарта (urn:google:locale
) и рисунка (urn:google:picture
) для входа ApplicationUser
, включая утверждение для GivenName:
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage =
"Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = new IdentityUser
{
UserName = Input.Email,
Email = Input.Email
};
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
// If they exist, add claims to the user for:
// Given (first) name
// Locale
// Picture
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst(ClaimTypes.GivenName));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:locale"));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:picture"));
}
// Include the access token in the properties
var props = new AuthenticationProperties();
props.StoreTokens(info.AuthenticationTokens);
props.IsPersistent = true;
await _signInManager.SignInAsync(user, props);
_logger.LogInformation(
"User created an account using {Name} provider.",
info.LoginProvider);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
LoginProvider = info.LoginProvider;
ReturnUrl = returnUrl;
return Page();
}
По умолчанию утверждения пользователя хранятся в проверке подлинности cookie. Если проверка подлинности cookie слишком велика, это может привести к сбою приложения, так как:
- Браузер обнаруживает, что cookie заголовок слишком длинный.
- Общий размер запроса слишком велик.
Если для обработки запросов пользователей требуется большое количество данных пользователя:
- Ограничьте количество и размер утверждений пользователей для обработки запросов только тем, что требуется приложению.
- Используйте настраиваемый ITicketStore для по промежуточного Cookie слоя SessionStore проверки подлинности для хранения identity между запросами. Сохраняйте большие объемы identity информации на сервере, отправляя клиенту только небольшой ключ идентификатора сеанса.
Сохранение маркера доступа
SaveTokens определяет, следует ли хранить маркеры доступа и обновления в AuthenticationProperties после успешной авторизации. SaveTokens
По умолчанию устанавливается значение false
, чтобы уменьшить размер окончательной проверки подлинности cookie.
Пример приложения задает значение SaveTokens
true
в GoogleOptions:
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
// Provide the Google Client Secret
options.ClientSecret = "{Client Secret}";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
При OnPostConfirmationAsync
выполнении сохраните маркер доступа (ExternalLoginInfo.AuthenticationTokens) из внешнего поставщика в ApplicationUser
's AuthenticationProperties
.
Пример приложения сохраняет маркер доступа в OnPostConfirmationAsync
(новая регистрация пользователя) и OnGetCallbackAsync
(ранее зарегистрированный пользователь) в 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();
}
Примечание.
Сведения о передаче маркеров Razor компонентам серверного приложения см. в дополнительных сценариях безопасности на стороне Blazor сервера ASP.NET CoreBlazor.
Добавление дополнительных пользовательских маркеров
Чтобы продемонстрировать, как добавить пользовательский маркер, который хранится в составеSaveTokens
, пример приложения добавляет текущее DateTime AuthenticationToken значение для AuthenticationToken.NameTicketCreated
:
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;
};
});
Создание и добавление утверждений
Платформа предоставляет общие действия и методы расширения для создания и добавления утверждений в коллекцию. Дополнительные сведения см. в разделах ClaimActionCollectionMapExtensions и ClaimActionCollectionUniqueExtensions.
Пользователи могут определять пользовательские действия, исходя из ClaimAction абстрактного Run метода и реализуя его.
Дополнительные сведения см. в разделе Microsoft.AspNetCore.Authentication.OAuth.Claims.
Добавление и обновление утверждений пользователей
Утверждения копируются из внешних поставщиков в пользовательную базу данных при первой регистрации, а не при входе. Если в приложении включены дополнительные утверждения после регистрации пользователя для использования приложения, вызовите SignInManager.RefreshSignInAsync для пользователя принудительное создание новой проверки подлинности cookie.
В среде разработки, работающей с тестовых учетных записей пользователей, можно просто удалить и повторно создать учетную запись пользователя. Для рабочих систем новые утверждения, добавленные в приложение, можно заполнить в учетные записи пользователей. После создания шаблонов страницы ExternalLogin
в приложение Areas/Pages/Identity/Account/Manage
добавьте следующий код ExternalLoginModel
в ExternalLogin.cshtml.cs
файл.
Добавьте словарь добавленных утверждений. Используйте ключи словаря для хранения типов утверждений и использования значений для хранения значения по умолчанию. Добавьте следующую строку в начало класса. В следующем примере предполагается, что одно утверждение добавляется для изображения Google пользователя с универсальным изображением headshot в качестве значения по умолчанию:
private readonly IReadOnlyDictionary<string, string> _claimsToSync =
new Dictionary<string, string>()
{
{ "urn:google:picture", "https://localhost:5001/headshot.png" },
};
Замените код OnGetCallbackAsync
метода по умолчанию следующим кодом. Код циклит по словарю утверждений. Утверждения добавляются (резервная копия) или обновляются для каждого пользователя. При добавлении или обновлении утверждений вход пользователя обновляется с помощью SignInManager<TUser>существующего свойства проверки подлинности (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();
}
}
Аналогичный подход применяется при изменении утверждений во время входа пользователя, но шаг обратной заполнения не требуется. Чтобы обновить утверждения пользователя, вызовите следующее для пользователя:
- UserManager.ReplaceClaimAsync для пользователей утверждений identity , хранящихся в базе данных.
- SignInManager.RefreshSignInAsync для пользователя, чтобы принудительно создать новую проверку подлинности cookie.
Удаление действий и утверждений утверждений
ClaimActionCollection.Remove(String) удаляет все действия утверждения для заданной ClaimType коллекции. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) удаляет утверждение заданного ClaimType объекта.identity DeleteClaim в основном используется с OpenID Connect (OIDC) для удаления утверждений, созданных протоколом.
Пример выходных данных приложения
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
Переадресация сведений запроса с помощью прокси-сервера или подсистемы балансировки нагрузки
Если приложение развертывается с прокси-сервером или подсистемой балансировки нагрузки, некоторые сведения из исходного запроса можно перенаправить в приложение в заголовках запроса. Эти сведения обычно включают безопасную схему запроса (https
), узел и IP-адрес клиента. Приложения не считывают автоматически и не используют эти заголовки запроса.
Схема используется для создания ссылок, определяющих процесс проверки подлинности с помощью внешних поставщиков. Потеря безопасной схемы (https
) приводит к тому, что приложение неправильно выполняет перенаправление на небезопасные URL-адреса.
Используйте ПО промежуточного слоя для перенаправленных заголовков, чтобы предоставить приложению сведения из исходных запросов для обработки запросов.
Дополнительные сведения см. в разделе Настройка ASP.NET Core для работы с прокси-серверами и подсистемами балансировки нагрузки.
Приложение ASP.NET Core может устанавливать дополнительные утверждения и маркеры от внешних поставщиков проверки подлинности, таких как Facebook, Google, Microsoft и Twitter. Каждый поставщик показывает различные сведения о пользователях на своей платформе, но шаблон получения и преобразования данных пользователей в дополнительные утверждения совпадает.
Просмотреть или скачать образец кода (описание загрузки)
Необходимые компоненты
Определите, какие внешние поставщики проверки подлинности будут поддерживать в приложении. Для каждого поставщика зарегистрируйте приложение и получите идентификатор клиента и секрет клиента. Дополнительные сведения см. в статье "Проверка подлинности Facebook и Google" в ASP.NET Core. В примере приложения используется поставщик проверки подлинности Google.
Установка идентификатора клиента и секрета клиента
Поставщик проверки подлинности OAuth устанавливает отношение доверия с приложением с помощью идентификатора клиента и секрета клиента. Идентификатор клиента и значения секрета клиента создаются внешним поставщиком проверки подлинности при регистрации приложения в поставщике. Каждый внешний поставщик, который использует приложение, должен быть настроен независимо с идентификатором клиента поставщика и секретом клиента. Дополнительные сведения см. в разделах внешнего поставщика проверки подлинности, которые применяются к вашему сценарию:
- Проверка подлинности Facebook
- Проверка подлинности Google
- Проверка подлинности Майкрософт
- Проверка подлинности Twitter
- Другие поставщики проверки подлинности
- OpenIdConnect
Пример приложения настраивает поставщика проверки подлинности Google с идентификатором клиента и секретом клиента, предоставленным 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;
};
});
Установка области проверки подлинности
Укажите список разрешений для получения от поставщика, указав параметр Scope. Области проверки подлинности для распространенных внешних поставщиков отображаются в следующей таблице.
Provider | Область |
---|---|
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 |
В примере приложения область Google userinfo.profile
автоматически добавляется платформой при AddGoogle вызове AuthenticationBuilder. Если приложению требуются дополнительные области, добавьте их в параметры. В следующем примере область Google https://www.googleapis.com/auth/user.birthday.read
добавляется для получения дня рождения пользователя:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
Сопоставление ключей данных пользователя и создание утверждений
В параметрах поставщика укажите MapJsonKey MapJsonSubKey или для каждого ключа или подраздела в пользовательских данных JSON внешнего поставщика, чтобы приложение identity читалось при входе. Дополнительные сведения о типах утверждений см. в разделе ClaimTypes.
Пример приложения создает утверждения языкового стандарта () и рисунка locale
(urn:google:locale
urn:google:picture
) из данных пользователя Google и picture
ключей:
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;
};
});
В Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync
, а IdentityUser (ApplicationUser
) войдет в приложение с SignInAsyncпомощью . Во время входа в систему UserManager<TUser> можно хранить ApplicationUser
утверждения для пользовательских данных, доступных на сайте Principal.
В примере приложения OnPostConfirmationAsync
(Account/ExternalLogin.cshtml.cs
) устанавливает утверждения языкового стандарта (urn:google:locale
) и рисунка (urn:google:picture
) для входа ApplicationUser
, включая утверждение для GivenName:
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage =
"Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = new IdentityUser
{
UserName = Input.Email,
Email = Input.Email
};
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
// If they exist, add claims to the user for:
// Given (first) name
// Locale
// Picture
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst(ClaimTypes.GivenName));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:locale"));
}
if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
{
await _userManager.AddClaimAsync(user,
info.Principal.FindFirst("urn:google:picture"));
}
// Include the access token in the properties
var props = new AuthenticationProperties();
props.StoreTokens(info.AuthenticationTokens);
props.IsPersistent = true;
await _signInManager.SignInAsync(user, props);
_logger.LogInformation(
"User created an account using {Name} provider.",
info.LoginProvider);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
LoginProvider = info.LoginProvider;
ReturnUrl = returnUrl;
return Page();
}
По умолчанию утверждения пользователя хранятся в проверке подлинности cookie. Если проверка подлинности cookie слишком велика, это может привести к сбою приложения, так как:
- Браузер обнаруживает, что cookie заголовок слишком длинный.
- Общий размер запроса слишком велик.
Если для обработки запросов пользователей требуется большое количество данных пользователя:
- Ограничьте количество и размер утверждений пользователей для обработки запросов только тем, что требуется приложению.
- Используйте настраиваемый ITicketStore для по промежуточного Cookie слоя SessionStore проверки подлинности для хранения identity между запросами. Сохраняйте большие объемы identity информации на сервере, отправляя клиенту только небольшой ключ идентификатора сеанса.
Сохранение маркера доступа
SaveTokens определяет, следует ли хранить маркеры доступа и обновления в AuthenticationProperties после успешной авторизации. SaveTokens
По умолчанию устанавливается значение false
, чтобы уменьшить размер окончательной проверки подлинности cookie.
Пример приложения задает значение SaveTokens
true
в GoogleOptions:
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
// Provide the Google Client Secret
options.ClientSecret = "{Client Secret}";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
При OnPostConfirmationAsync
выполнении сохраните маркер доступа (ExternalLoginInfo.AuthenticationTokens) из внешнего поставщика в ApplicationUser
's AuthenticationProperties
.
Пример приложения сохраняет маркер доступа в OnPostConfirmationAsync
(новая регистрация пользователя) и OnGetCallbackAsync
(ранее зарегистрированный пользователь) в 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();
}
Добавление дополнительных пользовательских маркеров
Чтобы продемонстрировать, как добавить пользовательский маркер, который хранится в составеSaveTokens
, пример приложения добавляет текущее DateTime AuthenticationToken значение для AuthenticationToken.NameTicketCreated
:
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;
};
});
Создание и добавление утверждений
Платформа предоставляет общие действия и методы расширения для создания и добавления утверждений в коллекцию. Дополнительные сведения см. в разделах ClaimActionCollectionMapExtensions и ClaimActionCollectionUniqueExtensions.
Пользователи могут определять пользовательские действия, исходя из ClaimAction абстрактного Run метода и реализуя его.
Дополнительные сведения см. в разделе Microsoft.AspNetCore.Authentication.OAuth.Claims.
Удаление действий и утверждений утверждений
ClaimActionCollection.Remove(String) удаляет все действия утверждения для заданной ClaimType коллекции. ClaimActionCollectionMapExtensions.DeleteClaim(ClaimActionCollection, String) удаляет утверждение заданного ClaimType объекта.identity DeleteClaim в основном используется с OpenID Connect (OIDC) для удаления утверждений, созданных протоколом.
Пример выходных данных приложения
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
Переадресация сведений запроса с помощью прокси-сервера или подсистемы балансировки нагрузки
Если приложение развертывается с прокси-сервером или подсистемой балансировки нагрузки, некоторые сведения из исходного запроса можно перенаправить в приложение в заголовках запроса. Эти сведения обычно включают безопасную схему запроса (https
), узел и IP-адрес клиента. Приложения не считывают автоматически и не используют эти заголовки запроса.
Схема используется для создания ссылок, определяющих процесс проверки подлинности с помощью внешних поставщиков. Потеря безопасной схемы (https
) приводит к тому, что приложение неправильно выполняет перенаправление на небезопасные URL-адреса.
Используйте ПО промежуточного слоя для перенаправленных заголовков, чтобы предоставить приложению сведения из исходных запросов для обработки запросов.
Дополнительные сведения см. в разделе Настройка ASP.NET Core для работы с прокси-серверами и подсистемами балансировки нагрузки.
Дополнительные ресурсы
- dotnet/AspNetCore engineering SocialSample app: связанное приложение находится в конструкторской ветви репозитория
main
dotnet/AspNetCore GitHub. Ветвьmain
содержит код при активной разработке для следующего выпуска ASP.NET Core. Чтобы просмотреть версию примера приложения для выпущенной версии ASP.NET Core, используйте раскрывающийся список Branch , чтобы выбрать ветвь выпуска (напримерrelease/{X.Y}
).
ASP.NET Core