在 ASP.NET Core 中保存外部提供程序的其他声明和令牌Persist additional claims and tokens from external providers in ASP.NET Core

本文内容

ASP.NET Core 应用可以从外部身份验证提供程序(如 Facebook、Google、Microsoft 和 Twitter)建立其他声明和令牌。每个提供程序都在其平台上显示有关用户的不同信息,但用于接收用户数据并将其转换为其他声明的模式是相同的。

查看或下载示例代码如何下载

必备条件Prerequisites

确定要在应用程序中支持的外部身份验证提供程序。对于每个提供程序,注册应用程序,并获取客户端 ID 和客户端密码。有关详细信息,请参阅 ASP.NET Core 中的 Facebook、Google 和外部提供程序身份验证示例应用使用Google 身份验证提供程序

设置客户端 ID 和客户端密码Set the client ID and client secret

OAuth 身份验证提供程序使用客户端 ID 和客户端密码与应用程序建立了信任关系。当向提供程序注册应用程序时,外部身份验证提供程序会为应用程序创建客户端 ID 和客户端机密值。应用使用的每个外部提供程序必须与提供程序的客户端 ID 和客户端机密一起单独配置。有关详细信息,请参阅适用于你的方案的外部身份验证提供程序主题:

示例应用使用 Google 提供的客户端 ID 和客户端机密配置 Google 身份验证提供程序:

  1. services.AddAuthentication().AddGoogle(options =>
  2. {
  3. // Provide the Google Client ID
  4. options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
  5. // Register with User Secrets using:
  6. // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
  7. // Provide the Google Client Secret
  8. options.ClientSecret = "{Client Secret}";
  9. // Register with User Secrets using:
  10. // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
  11. options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
  12. options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
  13. options.SaveTokens = true;
  14. options.Events.OnCreatingTicket = ctx =>
  15. {
  16. List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
  17. tokens.Add(new AuthenticationToken()
  18. {
  19. Name = "TicketCreated",
  20. Value = DateTime.UtcNow.ToString()
  21. });
  22. ctx.Properties.StoreTokens(tokens);
  23. return Task.CompletedTask;
  24. };
  25. });

建立身份验证范围Establish the authentication scope

通过指定 Scope指定要从提供程序检索的权限的列表。常见外部提供程序的身份验证范围如下表中所示。

提供程序范围
Facebookhttps://www.facebook.com/dialog/oauth
Googlehttps://www.googleapis.com/auth/userinfo.profile
Microsofthttps://login.microsoftonline.com/common/oauth2/v2.0/authorize
Twitterhttps://api.twitter.com/oauth/authenticate

在示例应用中,当对 AuthenticationBuilder调用 AddGoogle 时,框架会自动添加 Google 的 userinfo.profile 范围。如果应用需要其他作用域,请将它们添加到选项。在下面的示例中,添加了 Google https://www.googleapis.com/auth/user.birthday.read 范围以便检索用户的生日:

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

映射用户数据密钥并创建声明Map user data keys and create claims

在提供程序的选项中,为外部提供程序的 JSON 用户数据中的每个键/子项指定 MapJsonKeyMapJsonSubKey,以便在登录时读取应用程序标识。有关声明类型的详细信息,请参阅 ClaimTypes

该示例应用在 Google 用户数据中的 localepicture 密钥中创建区域设置(urn:google:locale)和图片(urn:google:picture)声明:

  1. services.AddAuthentication().AddGoogle(options =>
  2. {
  3. // Provide the Google Client ID
  4. options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
  5. // Register with User Secrets using:
  6. // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
  7. // Provide the Google Client Secret
  8. options.ClientSecret = "{Client Secret}";
  9. // Register with User Secrets using:
  10. // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
  11. options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
  12. options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
  13. options.SaveTokens = true;
  14. options.Events.OnCreatingTicket = ctx =>
  15. {
  16. List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
  17. tokens.Add(new AuthenticationToken()
  18. {
  19. Name = "TicketCreated",
  20. Value = DateTime.UtcNow.ToString()
  21. });
  22. ctx.Properties.StoreTokens(tokens);
  23. return Task.CompletedTask;
  24. };
  25. });

Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync中,IdentityUserApplicationUser)使用 SignInAsync登录到应用中。在登录过程中,UserManager<TUser> 可以存储 Principal提供的用户数据的 ApplicationUser 声明。

在示例应用中,OnPostConfirmationAsyncAccount/ExternalLogin)为已登录的 ApplicationUser建立了区域设置(urn:google:locale)和图片(urn:google:picture)声明,其中包括 GivenName的声明:

  1. public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
  2. {
  3. returnUrl = returnUrl ?? Url.Content("~/");
  4. // Get the information about the user from the external login provider
  5. var info = await _signInManager.GetExternalLoginInfoAsync();
  6. if (info == null)
  7. {
  8. ErrorMessage =
  9. "Error loading external login information during confirmation.";
  10. return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
  11. }
  12. if (ModelState.IsValid)
  13. {
  14. var user = new IdentityUser
  15. {
  16. UserName = Input.Email,
  17. Email = Input.Email
  18. };
  19. var result = await _userManager.CreateAsync(user);
  20. if (result.Succeeded)
  21. {
  22. result = await _userManager.AddLoginAsync(user, info);
  23. if (result.Succeeded)
  24. {
  25. // If they exist, add claims to the user for:
  26. // Given (first) name
  27. // Locale
  28. // Picture
  29. if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
  30. {
  31. await _userManager.AddClaimAsync(user,
  32. info.Principal.FindFirst(ClaimTypes.GivenName));
  33. }
  34. if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
  35. {
  36. await _userManager.AddClaimAsync(user,
  37. info.Principal.FindFirst("urn:google:locale"));
  38. }
  39. if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
  40. {
  41. await _userManager.AddClaimAsync(user,
  42. info.Principal.FindFirst("urn:google:picture"));
  43. }
  44. // Include the access token in the properties
  45. var props = new AuthenticationProperties();
  46. props.StoreTokens(info.AuthenticationTokens);
  47. props.IsPersistent = true;
  48. await _signInManager.SignInAsync(user, props);
  49. _logger.LogInformation(
  50. "User created an account using {Name} provider.",
  51. info.LoginProvider);
  52. return LocalRedirect(returnUrl);
  53. }
  54. }
  55. foreach (var error in result.Errors)
  56. {
  57. ModelState.AddModelError(string.Empty, error.Description);
  58. }
  59. }
  60. LoginProvider = info.LoginProvider;
  61. ReturnUrl = returnUrl;
  62. return Page();
  63. }

默认情况下,用户的声明存储在身份验证 cookie 中。如果身份验证 cookie 太大,则可能会导致应用程序失败,因为:

  • 浏览器检测到 cookie 标头太长。
  • 请求的整体大小太大。

如果需要大量用户数据来处理用户请求:

  • 将请求处理的用户声明的数量和大小限制为仅应用需要的内容。
  • 使用 Cookie 身份验证中间件 SessionStore 的自定义 ITicketStore 来跨请求存储标识。在服务器上保留大量标识信息,同时仅向客户端发送一个小型会话标识符密钥。

保存访问令牌Save the access token

SaveTokens 定义在授权成功后是否应将访问令牌和刷新令牌存储到 AuthenticationProperties 中。默认情况下,SaveTokens 设置为 false 以减小最终身份验证 cookie 的大小。

示例应用在 GoogleOptions中将 SaveTokens 的值设置为 true

  1. services.AddAuthentication().AddGoogle(options =>
  2. {
  3. // Provide the Google Client ID
  4. options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
  5. // Register with User Secrets using:
  6. // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
  7. // Provide the Google Client Secret
  8. options.ClientSecret = "{Client Secret}";
  9. // Register with User Secrets using:
  10. // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
  11. options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
  12. options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
  13. options.SaveTokens = true;
  14. options.Events.OnCreatingTicket = ctx =>
  15. {
  16. List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
  17. tokens.Add(new AuthenticationToken()
  18. {
  19. Name = "TicketCreated",
  20. Value = DateTime.UtcNow.ToString()
  21. });
  22. ctx.Properties.StoreTokens(tokens);
  23. return Task.CompletedTask;
  24. };
  25. });

OnPostConfirmationAsync 执行时,从 ApplicationUserAuthenticationProperties中的外部提供程序存储访问令牌(AuthenticationTokens)。

示例应用将访问令牌保存在帐户/ExternalLoginOnPostConfirmationAsync (新用户注册)和 OnGetCallbackAsync (以前注册的用户)中:

  1. public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
  2. {
  3. returnUrl = returnUrl ?? Url.Content("~/");
  4. // Get the information about the user from the external login provider
  5. var info = await _signInManager.GetExternalLoginInfoAsync();
  6. if (info == null)
  7. {
  8. ErrorMessage =
  9. "Error loading external login information during confirmation.";
  10. return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
  11. }
  12. if (ModelState.IsValid)
  13. {
  14. var user = new IdentityUser
  15. {
  16. UserName = Input.Email,
  17. Email = Input.Email
  18. };
  19. var result = await _userManager.CreateAsync(user);
  20. if (result.Succeeded)
  21. {
  22. result = await _userManager.AddLoginAsync(user, info);
  23. if (result.Succeeded)
  24. {
  25. // If they exist, add claims to the user for:
  26. // Given (first) name
  27. // Locale
  28. // Picture
  29. if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
  30. {
  31. await _userManager.AddClaimAsync(user,
  32. info.Principal.FindFirst(ClaimTypes.GivenName));
  33. }
  34. if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
  35. {
  36. await _userManager.AddClaimAsync(user,
  37. info.Principal.FindFirst("urn:google:locale"));
  38. }
  39. if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
  40. {
  41. await _userManager.AddClaimAsync(user,
  42. info.Principal.FindFirst("urn:google:picture"));
  43. }
  44. // Include the access token in the properties
  45. var props = new AuthenticationProperties();
  46. props.StoreTokens(info.AuthenticationTokens);
  47. props.IsPersistent = true;
  48. await _signInManager.SignInAsync(user, props);
  49. _logger.LogInformation(
  50. "User created an account using {Name} provider.",
  51. info.LoginProvider);
  52. return LocalRedirect(returnUrl);
  53. }
  54. }
  55. foreach (var error in result.Errors)
  56. {
  57. ModelState.AddModelError(string.Empty, error.Description);
  58. }
  59. }
  60. LoginProvider = info.LoginProvider;
  61. ReturnUrl = returnUrl;
  62. return Page();
  63. }

如何添加其他自定义令牌How to add additional custom tokens

为了演示如何添加作为 SaveTokens的一部分存储的自定义令牌,示例应用添加了一个 AuthenticationToken,其中包含当前 DateTimeAuthenticationToken.NameTicketCreated

  1. services.AddAuthentication().AddGoogle(options =>
  2. {
  3. // Provide the Google Client ID
  4. options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
  5. // Register with User Secrets using:
  6. // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
  7. // Provide the Google Client Secret
  8. options.ClientSecret = "{Client Secret}";
  9. // Register with User Secrets using:
  10. // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
  11. options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
  12. options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
  13. options.SaveTokens = true;
  14. options.Events.OnCreatingTicket = ctx =>
  15. {
  16. List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
  17. tokens.Add(new AuthenticationToken()
  18. {
  19. Name = "TicketCreated",
  20. Value = DateTime.UtcNow.ToString()
  21. });
  22. ctx.Properties.StoreTokens(tokens);
  23. return Task.CompletedTask;
  24. };
  25. });

创建和添加声明Creating and adding claims

框架提供用于创建声明并将其添加到集合的常见操作和扩展方法。有关详细信息,请参阅 ClaimActionCollectionMapExtensionsClaimActionCollectionUniqueExtensions

用户可以通过从 ClaimAction 派生并实现抽象 Run 方法来定义自定义操作。

有关详细信息,请参阅 Microsoft.AspNetCore.Authentication.OAuth.Claims

删除声明操作和声明Removal of claim actions and claims

ClaimActionCollection (String)从集合中移除给定 ClaimType 的所有声明操作。ClaimActionCollectionMapExtensions. DeleteClaim (ClaimActionCollection,String)从标识中删除给定 ClaimType 的声明。DeleteClaim 主要与OpenID connect (OIDC)一起使用,以删除协议生成的声明。

示例应用程序输出Sample app output

  1. User Claims
  2. http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
  3. 9b342344f-7aab-43c2-1ac1-ba75912ca999
  4. http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
  5. someone@gmail.com
  6. AspNet.Identity.SecurityStamp
  7. 7D4312MOWRYYBFI1KXRPHGOSTBVWSFDE
  8. http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
  9. Judy
  10. urn:google:locale
  11. en
  12. urn:google:picture
  13. https://lh4.googleusercontent.com/-XXXXXX/XXXXXX/XXXXXX/XXXXXX/photo.jpg
  14. Authentication Properties
  15. .Token.access_token
  16. yc23.AlvoZqz56...1lxltXV7D-ZWP9
  17. .Token.token_type
  18. Bearer
  19. .Token.expires_at
  20. 2019-04-11T22:14:51.0000000+00:00
  21. .Token.TicketCreated
  22. 4/11/2019 9:14:52 PM
  23. .TokenNames
  24. access_token;token_type;expires_at;TicketCreated
  25. .persistent
  26. .issued
  27. Thu, 11 Apr 2019 20:51:06 GMT
  28. .expires
  29. Thu, 25 Apr 2019 20:51:06 GMT

使用代理或负载均衡器转发请求信息Forward request information with a proxy or load balancer

如果应用部署在代理服务器或负载均衡器后面,则可能会将某些原始请求信息转发到请求标头中的应用。此信息通常包括安全请求方案 (https)、主机和客户端 IP 地址。应用不会自动读取这些请求标头以发现和使用原始请求信息。

方案用于通过外部提供程序影响身份验证流的链接生成。丢失安全方案 (https) 会导致应用生成不正确且不安全的重定向 URL。

使用转发标头中间件以使应用可以使用原始请求信息来进行请求处理。

有关详细信息,请参阅 配置 ASP.NET Core 以使用代理服务器和负载均衡器

ASP.NET Core 应用可以从外部身份验证提供程序(如 Facebook、Google、Microsoft 和 Twitter)建立其他声明和令牌。每个提供程序都在其平台上显示有关用户的不同信息,但用于接收用户数据并将其转换为其他声明的模式是相同的。

查看或下载示例代码如何下载

必备条件Prerequisites

确定要在应用程序中支持的外部身份验证提供程序。对于每个提供程序,注册应用程序,并获取客户端 ID 和客户端密码。有关详细信息,请参阅 ASP.NET Core 中的 Facebook、Google 和外部提供程序身份验证示例应用使用Google 身份验证提供程序

设置客户端 ID 和客户端密码Set the client ID and client secret

OAuth 身份验证提供程序使用客户端 ID 和客户端密码与应用程序建立了信任关系。当向提供程序注册应用程序时,外部身份验证提供程序会为应用程序创建客户端 ID 和客户端机密值。应用使用的每个外部提供程序必须与提供程序的客户端 ID 和客户端机密一起单独配置。有关详细信息,请参阅适用于你的方案的外部身份验证提供程序主题:

示例应用使用 Google 提供的客户端 ID 和客户端机密配置 Google 身份验证提供程序:

  1. services.AddAuthentication().AddGoogle(options =>
  2. {
  3. // Provide the Google Client ID
  4. options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
  5. // Register with User Secrets using:
  6. // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
  7. // Provide the Google Client Secret
  8. options.ClientSecret = "{Client Secret}";
  9. // Register with User Secrets using:
  10. // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
  11. options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
  12. options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
  13. options.SaveTokens = true;
  14. options.Events.OnCreatingTicket = ctx =>
  15. {
  16. List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
  17. tokens.Add(new AuthenticationToken()
  18. {
  19. Name = "TicketCreated",
  20. Value = DateTime.UtcNow.ToString()
  21. });
  22. ctx.Properties.StoreTokens(tokens);
  23. return Task.CompletedTask;
  24. };
  25. });

建立身份验证范围Establish the authentication scope

通过指定 Scope指定要从提供程序检索的权限的列表。常见外部提供程序的身份验证范围如下表中所示。

提供程序范围
Facebookhttps://www.facebook.com/dialog/oauth
Googlehttps://www.googleapis.com/auth/userinfo.profile
Microsofthttps://login.microsoftonline.com/common/oauth2/v2.0/authorize
Twitterhttps://api.twitter.com/oauth/authenticate

在示例应用中,当对 AuthenticationBuilder调用 AddGoogle 时,框架会自动添加 Google 的 userinfo.profile 范围。如果应用需要其他作用域,请将它们添加到选项。在下面的示例中,添加了 Google https://www.googleapis.com/auth/user.birthday.read 范围以便检索用户的生日:

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

映射用户数据密钥并创建声明Map user data keys and create claims

在提供程序的选项中,为外部提供程序的 JSON 用户数据中的每个键/子项指定 MapJsonKeyMapJsonSubKey,以便在登录时读取应用程序标识。有关声明类型的详细信息,请参阅 ClaimTypes

该示例应用在 Google 用户数据中的 localepicture 密钥中创建区域设置(urn:google:locale)和图片(urn:google:picture)声明:

  1. services.AddAuthentication().AddGoogle(options =>
  2. {
  3. // Provide the Google Client ID
  4. options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
  5. // Register with User Secrets using:
  6. // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
  7. // Provide the Google Client Secret
  8. options.ClientSecret = "{Client Secret}";
  9. // Register with User Secrets using:
  10. // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
  11. options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
  12. options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
  13. options.SaveTokens = true;
  14. options.Events.OnCreatingTicket = ctx =>
  15. {
  16. List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
  17. tokens.Add(new AuthenticationToken()
  18. {
  19. Name = "TicketCreated",
  20. Value = DateTime.UtcNow.ToString()
  21. });
  22. ctx.Properties.StoreTokens(tokens);
  23. return Task.CompletedTask;
  24. };
  25. });

Microsoft.AspNetCore.Identity.UI.Pages.Account.Internal.ExternalLoginModel.OnPostConfirmationAsync中,IdentityUserApplicationUser)使用 SignInAsync登录到应用中。在登录过程中,UserManager<TUser> 可以存储 Principal提供的用户数据的 ApplicationUser 声明。

在示例应用中,OnPostConfirmationAsyncAccount/ExternalLogin)为已登录的 ApplicationUser建立了区域设置(urn:google:locale)和图片(urn:google:picture)声明,其中包括 GivenName的声明:

  1. public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
  2. {
  3. returnUrl = returnUrl ?? Url.Content("~/");
  4. // Get the information about the user from the external login provider
  5. var info = await _signInManager.GetExternalLoginInfoAsync();
  6. if (info == null)
  7. {
  8. ErrorMessage =
  9. "Error loading external login information during confirmation.";
  10. return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
  11. }
  12. if (ModelState.IsValid)
  13. {
  14. var user = new IdentityUser
  15. {
  16. UserName = Input.Email,
  17. Email = Input.Email
  18. };
  19. var result = await _userManager.CreateAsync(user);
  20. if (result.Succeeded)
  21. {
  22. result = await _userManager.AddLoginAsync(user, info);
  23. if (result.Succeeded)
  24. {
  25. // If they exist, add claims to the user for:
  26. // Given (first) name
  27. // Locale
  28. // Picture
  29. if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
  30. {
  31. await _userManager.AddClaimAsync(user,
  32. info.Principal.FindFirst(ClaimTypes.GivenName));
  33. }
  34. if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
  35. {
  36. await _userManager.AddClaimAsync(user,
  37. info.Principal.FindFirst("urn:google:locale"));
  38. }
  39. if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
  40. {
  41. await _userManager.AddClaimAsync(user,
  42. info.Principal.FindFirst("urn:google:picture"));
  43. }
  44. // Include the access token in the properties
  45. var props = new AuthenticationProperties();
  46. props.StoreTokens(info.AuthenticationTokens);
  47. props.IsPersistent = true;
  48. await _signInManager.SignInAsync(user, props);
  49. _logger.LogInformation(
  50. "User created an account using {Name} provider.",
  51. info.LoginProvider);
  52. return LocalRedirect(returnUrl);
  53. }
  54. }
  55. foreach (var error in result.Errors)
  56. {
  57. ModelState.AddModelError(string.Empty, error.Description);
  58. }
  59. }
  60. LoginProvider = info.LoginProvider;
  61. ReturnUrl = returnUrl;
  62. return Page();
  63. }

默认情况下,用户的声明存储在身份验证 cookie 中。如果身份验证 cookie 太大,则可能会导致应用程序失败,因为:

  • 浏览器检测到 cookie 标头太长。
  • 请求的整体大小太大。

如果需要大量用户数据来处理用户请求:

  • 将请求处理的用户声明的数量和大小限制为仅应用需要的内容。
  • 使用 Cookie 身份验证中间件 SessionStore 的自定义 ITicketStore 来跨请求存储标识。在服务器上保留大量标识信息,同时仅向客户端发送一个小型会话标识符密钥。

保存访问令牌Save the access token

SaveTokens 定义在授权成功后是否应将访问令牌和刷新令牌存储到 AuthenticationProperties 中。默认情况下,SaveTokens 设置为 false 以减小最终身份验证 cookie 的大小。

示例应用在 GoogleOptions中将 SaveTokens 的值设置为 true

  1. services.AddAuthentication().AddGoogle(options =>
  2. {
  3. // Provide the Google Client ID
  4. options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
  5. // Register with User Secrets using:
  6. // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
  7. // Provide the Google Client Secret
  8. options.ClientSecret = "{Client Secret}";
  9. // Register with User Secrets using:
  10. // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
  11. options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
  12. options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
  13. options.SaveTokens = true;
  14. options.Events.OnCreatingTicket = ctx =>
  15. {
  16. List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
  17. tokens.Add(new AuthenticationToken()
  18. {
  19. Name = "TicketCreated",
  20. Value = DateTime.UtcNow.ToString()
  21. });
  22. ctx.Properties.StoreTokens(tokens);
  23. return Task.CompletedTask;
  24. };
  25. });

OnPostConfirmationAsync 执行时,从 ApplicationUserAuthenticationProperties中的外部提供程序存储访问令牌(AuthenticationTokens)。

示例应用将访问令牌保存在帐户/ExternalLoginOnPostConfirmationAsync (新用户注册)和 OnGetCallbackAsync (以前注册的用户)中:

  1. public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
  2. {
  3. returnUrl = returnUrl ?? Url.Content("~/");
  4. // Get the information about the user from the external login provider
  5. var info = await _signInManager.GetExternalLoginInfoAsync();
  6. if (info == null)
  7. {
  8. ErrorMessage =
  9. "Error loading external login information during confirmation.";
  10. return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
  11. }
  12. if (ModelState.IsValid)
  13. {
  14. var user = new IdentityUser
  15. {
  16. UserName = Input.Email,
  17. Email = Input.Email
  18. };
  19. var result = await _userManager.CreateAsync(user);
  20. if (result.Succeeded)
  21. {
  22. result = await _userManager.AddLoginAsync(user, info);
  23. if (result.Succeeded)
  24. {
  25. // If they exist, add claims to the user for:
  26. // Given (first) name
  27. // Locale
  28. // Picture
  29. if (info.Principal.HasClaim(c => c.Type == ClaimTypes.GivenName))
  30. {
  31. await _userManager.AddClaimAsync(user,
  32. info.Principal.FindFirst(ClaimTypes.GivenName));
  33. }
  34. if (info.Principal.HasClaim(c => c.Type == "urn:google:locale"))
  35. {
  36. await _userManager.AddClaimAsync(user,
  37. info.Principal.FindFirst("urn:google:locale"));
  38. }
  39. if (info.Principal.HasClaim(c => c.Type == "urn:google:picture"))
  40. {
  41. await _userManager.AddClaimAsync(user,
  42. info.Principal.FindFirst("urn:google:picture"));
  43. }
  44. // Include the access token in the properties
  45. var props = new AuthenticationProperties();
  46. props.StoreTokens(info.AuthenticationTokens);
  47. props.IsPersistent = true;
  48. await _signInManager.SignInAsync(user, props);
  49. _logger.LogInformation(
  50. "User created an account using {Name} provider.",
  51. info.LoginProvider);
  52. return LocalRedirect(returnUrl);
  53. }
  54. }
  55. foreach (var error in result.Errors)
  56. {
  57. ModelState.AddModelError(string.Empty, error.Description);
  58. }
  59. }
  60. LoginProvider = info.LoginProvider;
  61. ReturnUrl = returnUrl;
  62. return Page();
  63. }

如何添加其他自定义令牌How to add additional custom tokens

为了演示如何添加作为 SaveTokens的一部分存储的自定义令牌,示例应用添加了一个 AuthenticationToken,其中包含当前 DateTimeAuthenticationToken.NameTicketCreated

  1. services.AddAuthentication().AddGoogle(options =>
  2. {
  3. // Provide the Google Client ID
  4. options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
  5. // Register with User Secrets using:
  6. // dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
  7. // Provide the Google Client Secret
  8. options.ClientSecret = "{Client Secret}";
  9. // Register with User Secrets using:
  10. // dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
  11. options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
  12. options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
  13. options.SaveTokens = true;
  14. options.Events.OnCreatingTicket = ctx =>
  15. {
  16. List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
  17. tokens.Add(new AuthenticationToken()
  18. {
  19. Name = "TicketCreated",
  20. Value = DateTime.UtcNow.ToString()
  21. });
  22. ctx.Properties.StoreTokens(tokens);
  23. return Task.CompletedTask;
  24. };
  25. });

创建和添加声明Creating and adding claims

框架提供用于创建声明并将其添加到集合的常见操作和扩展方法。有关详细信息,请参阅 ClaimActionCollectionMapExtensionsClaimActionCollectionUniqueExtensions

用户可以通过从 ClaimAction 派生并实现抽象 Run 方法来定义自定义操作。

有关详细信息,请参阅 Microsoft.AspNetCore.Authentication.OAuth.Claims

删除声明操作和声明Removal of claim actions and claims

ClaimActionCollection (String)从集合中移除给定 ClaimType 的所有声明操作。ClaimActionCollectionMapExtensions. DeleteClaim (ClaimActionCollection,String)从标识中删除给定 ClaimType 的声明。DeleteClaim 主要与OpenID connect (OIDC)一起使用,以删除协议生成的声明。

示例应用程序输出Sample app output

  1. User Claims
  2. http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
  3. 9b342344f-7aab-43c2-1ac1-ba75912ca999
  4. http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
  5. someone@gmail.com
  6. AspNet.Identity.SecurityStamp
  7. 7D4312MOWRYYBFI1KXRPHGOSTBVWSFDE
  8. http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
  9. Judy
  10. urn:google:locale
  11. en
  12. urn:google:picture
  13. https://lh4.googleusercontent.com/-XXXXXX/XXXXXX/XXXXXX/XXXXXX/photo.jpg
  14. Authentication Properties
  15. .Token.access_token
  16. yc23.AlvoZqz56...1lxltXV7D-ZWP9
  17. .Token.token_type
  18. Bearer
  19. .Token.expires_at
  20. 2019-04-11T22:14:51.0000000+00:00
  21. .Token.TicketCreated
  22. 4/11/2019 9:14:52 PM
  23. .TokenNames
  24. access_token;token_type;expires_at;TicketCreated
  25. .persistent
  26. .issued
  27. Thu, 11 Apr 2019 20:51:06 GMT
  28. .expires
  29. Thu, 25 Apr 2019 20:51:06 GMT

使用代理或负载均衡器转发请求信息Forward request information with a proxy or load balancer

如果应用部署在代理服务器或负载均衡器后面,则可能会将某些原始请求信息转发到请求标头中的应用。此信息通常包括安全请求方案 (https)、主机和客户端 IP 地址。应用不会自动读取这些请求标头以发现和使用原始请求信息。

方案用于通过外部提供程序影响身份验证流的链接生成。丢失安全方案 (https) 会导致应用生成不正确且不安全的重定向 URL。

使用转发标头中间件以使应用可以使用原始请求信息来进行请求处理。

有关详细信息,请参阅 配置 ASP.NET Core 以使用代理服务器和负载均衡器

其他资源Additional resources

  • dotnet/AspNetCore 工程 SocialSample 应用– 链接的示例应用位于Dotnet/AspNetCore GitHub存储库的 master 工程分支。对于 ASP.NET Core 的下一版本,master 分支包含处于活动开发下的代码。若要查看 ASP.NET Core 的已发布版本的示例应用的版本,请使用分支下拉列表选择发布分支(例如 release/{X.Y})。