参考:Microsoft 帐户外部登录设置与 ASP.NET Core
启用 Microsoft 身份验证
为了保证兼容性,需先安装 2.2.0 版本:
然后在 appsettings.json 中配置 ClientId 和 ClientSecret。
再在 Startup 中进行配置:
services.AddAuthentication().AddMicrosoftAccount(opt =>
{
opt.ClientId = _configuration["Authentication:Microsoft:ClientId"];
opt.ClientSecret = _configuration["Authentication:Microsoft:ClientSecret"];
});
修改 LoginViewModel:
/// <summary>
/// 登录视图模型
/// </summary>
public class LoginViewModel
{
[Required(ErrorMessage = "邮箱地址不能为空")]
[EmailAddress]
[Display(Name = "邮箱地址")]
public string Email { get; set; }
[Required(ErrorMessage = "密码不能为空")]
[DataType(DataType.Password)]
[Display(Name = "密码")]
public string Password { get; set; }
[Display(Name = "记住我")]
public bool RememberMe { get; set; }
public string ReturnUrl { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
}
Login.cshtml 添加扩展登录按钮:
<div class="col-md-6">
<h1>扩展登录</h1>
<form class="mt-3" method="post" asp-action="ExternalLogin" asp-controller="Account" asp-route-returnUrl="@Model.ReturnUrl">
<div>
@foreach (var provider in Model.ExternalLogins)
{
<button type="submit" class="btn btn-info" name="provider" style="width: auto"
value="@provider.Name" title="Log in using your @provider.DisplayName account">
@provider.DisplayName
</button>
}
</div>
</form>
</div>
修改 AccountController 中的代码:
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Login(string returnUrl)
{
var model = new LoginViewModel
{
ReturnUrl = returnUrl,
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList()
};
return View(model);
}
...
#region 扩展登录
[HttpPost]
public IActionResult ExternalLogin(string provider, string returnUrl)
{
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
var loginViewModel = new LoginViewModel
{
ReturnUrl = returnUrl,
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList()
};
if (remoteError != null)
{
ModelState.AddModelError(string.Empty, $"外部提供程序错误: {remoteError}");
return View("Login", loginViewModel);
}
// 从外部登录提供者,即微软账户体系中,获取关于用户的登录信息。
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ModelState.AddModelError(string.Empty, "加载外部登录信息出错。");
return View("Login", loginViewModel);
}
//如果用户之前已经登录过了,会在 AspNetUserLogins 表有对应的记录,这个时候无需创建新的记录,直接使用当前记录登录系统即可。
var signInResult = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey,
isPersistent: false, bypassTwoFactor: true);
if (signInResult.Succeeded)
{
return LocalRedirect(returnUrl);
}
// 如果 AspNetUserLogins 表中没有记录,则代表用户没有一个本地帐户,这个时候我们就需要创建一个记录了。
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
if (email != null)
{
// 通过邮箱地址去查询用户是否已存在
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
user = new ApplicationUser
{
UserName = info.Principal.FindFirstValue(ClaimTypes.Email),
Email = info.Principal.FindFirstValue(ClaimTypes.Email)
};
// 如果不存在,则创建一个用户,但是这个用户没有密码。
await _userManager.CreateAsync(user);
}
// 在 AspNetUserLogins 表中,添加一行用户数据,然后将当前用户登录到系统中
await _userManager.AddLoginAsync(user, info);
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
// 如果我们获取不到电子邮件地址,我们需要将请求重定向到错误视图中。
ViewBag.ErrorTitle = $"我们无法从提供商:{info.LoginProvider}中解析到您的邮件地址 ";
ViewBag.ErrorMessage = "请通过联系 ltm@ddxc.org 寻求技术支持。";
return View("Error");
}
#endregion
解决“我们无法完成你的请求”
遇到这个奇葩问题,是因为 ClientId 设置错误。
首先,ClientSecret 确实是 证书和密码 - 客户端密码 里面的 值。但我们需要的 ClientId 不是此处的 ID,而是概述界面中的 应用程序(客户端)ID。