- 在 ASP.NET Core 中保存外部提供程序的其他声明和令牌Persist additional claims and tokens from external providers in ASP.NET Core
- 必备条件Prerequisites
- 设置客户端 ID 和客户端密码Set the client ID and client secret
- 建立身份验证范围Establish the authentication scope
- 映射用户数据密钥并创建声明Map user data keys and create claims
- 保存访问令牌Save the access token
- 如何添加其他自定义令牌How to add additional custom tokens
- 创建和添加声明Creating and adding claims
- 删除声明操作和声明Removal of claim actions and claims
- 示例应用程序输出Sample app output
- 使用代理或负载均衡器转发请求信息Forward request information with a proxy or load balancer
- 必备条件Prerequisites
- 设置客户端 ID 和客户端密码Set the client ID and client secret
- 建立身份验证范围Establish the authentication scope
- 映射用户数据密钥并创建声明Map user data keys and create claims
- 保存访问令牌Save the access token
- 如何添加其他自定义令牌How to add additional custom tokens
- 创建和添加声明Creating and adding claims
- 删除声明操作和声明Removal of claim actions and claims
- 示例应用程序输出Sample app output
- 使用代理或负载均衡器转发请求信息Forward request information with a proxy or load balancer
- 其他资源Additional resources
在 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 身份验证提供程序:
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;
};
});
建立身份验证范围Establish the authentication scope
通过指定 Scope指定要从提供程序检索的权限的列表。常见外部提供程序的身份验证范围如下表中所示。
在示例应用中,当对 AuthenticationBuilder调用 AddGoogle 时,框架会自动添加 Google 的 userinfo.profile
范围。如果应用需要其他作用域,请将它们添加到选项。在下面的示例中,添加了 Google https://www.googleapis.com/auth/user.birthday.read
范围以便检索用户的生日:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
映射用户数据密钥并创建声明Map user data keys and create claims
在提供程序的选项中,为外部提供程序的 JSON 用户数据中的每个键/子项指定 MapJsonKey 或 MapJsonSubKey,以便在登录时读取应用程序标识。有关声明类型的详细信息,请参阅 ClaimTypes。
该示例应用在 Google 用户数据中的 locale
和 picture
密钥中创建区域设置(urn:google:locale
)和图片(urn: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> 可以存储 Principal提供的用户数据的 ApplicationUser
声明。
在示例应用中,OnPostConfirmationAsync
(Account/ExternalLogin)为已登录的 ApplicationUser
建立了区域设置(urn:google:locale
)和图片(urn:google:picture
)声明,其中包括 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 标头太长。
- 请求的整体大小太大。
如果需要大量用户数据来处理用户请求:
- 将请求处理的用户声明的数量和大小限制为仅应用需要的内容。
- 使用 Cookie 身份验证中间件 SessionStore 的自定义 ITicketStore 来跨请求存储标识。在服务器上保留大量标识信息,同时仅向客户端发送一个小型会话标识符密钥。
保存访问令牌Save the access token
SaveTokens 定义在授权成功后是否应将访问令牌和刷新令牌存储到 AuthenticationProperties 中。默认情况下,SaveTokens
设置为 false
以减小最终身份验证 cookie 的大小。
示例应用在 GoogleOptions中将 SaveTokens
的值设置为 true
:
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
执行时,从 ApplicationUser
的 AuthenticationProperties
中的外部提供程序存储访问令牌(AuthenticationTokens)。
示例应用将访问令牌保存在帐户/ExternalLogin的 OnPostConfirmationAsync
(新用户注册)和 OnGetCallbackAsync
(以前注册的用户)中:
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();
}
如何添加其他自定义令牌How to add additional custom tokens
为了演示如何添加作为 SaveTokens
的一部分存储的自定义令牌,示例应用添加了一个 AuthenticationToken,其中包含当前 DateTime 的AuthenticationToken.Name为 TicketCreated
:
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
// Provide the Google Client Secret
options.ClientSecret = "{Client Secret}";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
创建和添加声明Creating and adding claims
框架提供用于创建声明并将其添加到集合的常见操作和扩展方法。有关详细信息,请参阅 ClaimActionCollectionMapExtensions 和 ClaimActionCollectionUniqueExtensions。
用户可以通过从 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
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
使用代理或负载均衡器转发请求信息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 身份验证提供程序:
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;
};
});
建立身份验证范围Establish the authentication scope
通过指定 Scope指定要从提供程序检索的权限的列表。常见外部提供程序的身份验证范围如下表中所示。
在示例应用中,当对 AuthenticationBuilder调用 AddGoogle 时,框架会自动添加 Google 的 userinfo.profile
范围。如果应用需要其他作用域,请将它们添加到选项。在下面的示例中,添加了 Google https://www.googleapis.com/auth/user.birthday.read
范围以便检索用户的生日:
options.Scope.Add("https://www.googleapis.com/auth/user.birthday.read");
映射用户数据密钥并创建声明Map user data keys and create claims
在提供程序的选项中,为外部提供程序的 JSON 用户数据中的每个键/子项指定 MapJsonKey 或 MapJsonSubKey,以便在登录时读取应用程序标识。有关声明类型的详细信息,请参阅 ClaimTypes。
该示例应用在 Google 用户数据中的 locale
和 picture
密钥中创建区域设置(urn:google:locale
)和图片(urn: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> 可以存储 Principal提供的用户数据的 ApplicationUser
声明。
在示例应用中,OnPostConfirmationAsync
(Account/ExternalLogin)为已登录的 ApplicationUser
建立了区域设置(urn:google:locale
)和图片(urn:google:picture
)声明,其中包括 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 标头太长。
- 请求的整体大小太大。
如果需要大量用户数据来处理用户请求:
- 将请求处理的用户声明的数量和大小限制为仅应用需要的内容。
- 使用 Cookie 身份验证中间件 SessionStore 的自定义 ITicketStore 来跨请求存储标识。在服务器上保留大量标识信息,同时仅向客户端发送一个小型会话标识符密钥。
保存访问令牌Save the access token
SaveTokens 定义在授权成功后是否应将访问令牌和刷新令牌存储到 AuthenticationProperties 中。默认情况下,SaveTokens
设置为 false
以减小最终身份验证 cookie 的大小。
示例应用在 GoogleOptions中将 SaveTokens
的值设置为 true
:
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
执行时,从 ApplicationUser
的 AuthenticationProperties
中的外部提供程序存储访问令牌(AuthenticationTokens)。
示例应用将访问令牌保存在帐户/ExternalLogin的 OnPostConfirmationAsync
(新用户注册)和 OnGetCallbackAsync
(以前注册的用户)中:
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();
}
如何添加其他自定义令牌How to add additional custom tokens
为了演示如何添加作为 SaveTokens
的一部分存储的自定义令牌,示例应用添加了一个 AuthenticationToken,其中包含当前 DateTime 的AuthenticationToken.Name为 TicketCreated
:
services.AddAuthentication().AddGoogle(options =>
{
// Provide the Google Client ID
options.ClientId = "XXXXXXXXXXXXXXX.apps.googleusercontent.com";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientId" "{Client ID}"
// Provide the Google Client Secret
options.ClientSecret = "{Client Secret}";
// Register with User Secrets using:
// dotnet user-secrets set "Authentication:Google:ClientSecret" "{Client Secret}"
options.ClaimActions.MapJsonKey("urn:google:picture", "picture", "url");
options.ClaimActions.MapJsonKey("urn:google:locale", "locale", "string");
options.SaveTokens = true;
options.Events.OnCreatingTicket = ctx =>
{
List<AuthenticationToken> tokens = ctx.Properties.GetTokens().ToList();
tokens.Add(new AuthenticationToken()
{
Name = "TicketCreated",
Value = DateTime.UtcNow.ToString()
});
ctx.Properties.StoreTokens(tokens);
return Task.CompletedTask;
};
});
创建和添加声明Creating and adding claims
框架提供用于创建声明并将其添加到集合的常见操作和扩展方法。有关详细信息,请参阅 ClaimActionCollectionMapExtensions 和 ClaimActionCollectionUniqueExtensions。
用户可以通过从 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
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
使用代理或负载均衡器转发请求信息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}
)。