参考:Microsoft 帐户外部登录设置与 ASP.NET Core
image.png

启用 Microsoft 身份验证

为了保证兼容性,需先安装 2.2.0 版本:
image.png
然后在 appsettings.json 中配置 ClientId 和 ClientSecret。

再在 Startup 中进行配置:

  1. services.AddAuthentication().AddMicrosoftAccount(opt =>
  2. {
  3. opt.ClientId = _configuration["Authentication:Microsoft:ClientId"];
  4. opt.ClientSecret = _configuration["Authentication:Microsoft:ClientSecret"];
  5. });

修改 LoginViewModel:

  1. /// <summary>
  2. /// 登录视图模型
  3. /// </summary>
  4. public class LoginViewModel
  5. {
  6. [Required(ErrorMessage = "邮箱地址不能为空")]
  7. [EmailAddress]
  8. [Display(Name = "邮箱地址")]
  9. public string Email { get; set; }
  10. [Required(ErrorMessage = "密码不能为空")]
  11. [DataType(DataType.Password)]
  12. [Display(Name = "密码")]
  13. public string Password { get; set; }
  14. [Display(Name = "记住我")]
  15. public bool RememberMe { get; set; }
  16. public string ReturnUrl { get; set; }
  17. public IList<AuthenticationScheme> ExternalLogins { get; set; }
  18. }

Login.cshtml 添加扩展登录按钮:

  1. <div class="col-md-6">
  2. <h1>扩展登录</h1>
  3. <form class="mt-3" method="post" asp-action="ExternalLogin" asp-controller="Account" asp-route-returnUrl="@Model.ReturnUrl">
  4. <div>
  5. @foreach (var provider in Model.ExternalLogins)
  6. {
  7. <button type="submit" class="btn btn-info" name="provider" style="width: auto"
  8. value="@provider.Name" title="Log in using your @provider.DisplayName account">
  9. @provider.DisplayName
  10. </button>
  11. }
  12. </div>
  13. </form>
  14. </div>

修改 AccountController 中的代码:

  1. [HttpGet]
  2. [AllowAnonymous]
  3. public async Task<IActionResult> Login(string returnUrl)
  4. {
  5. var model = new LoginViewModel
  6. {
  7. ReturnUrl = returnUrl,
  8. ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList()
  9. };
  10. return View(model);
  11. }
  12. ...
  13. #region 扩展登录
  14. [HttpPost]
  15. public IActionResult ExternalLogin(string provider, string returnUrl)
  16. {
  17. var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
  18. var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
  19. return new ChallengeResult(provider, properties);
  20. }
  21. public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
  22. {
  23. returnUrl = returnUrl ?? Url.Content("~/");
  24. var loginViewModel = new LoginViewModel
  25. {
  26. ReturnUrl = returnUrl,
  27. ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList()
  28. };
  29. if (remoteError != null)
  30. {
  31. ModelState.AddModelError(string.Empty, $"外部提供程序错误: {remoteError}");
  32. return View("Login", loginViewModel);
  33. }
  34. // 从外部登录提供者,即微软账户体系中,获取关于用户的登录信息。
  35. var info = await _signInManager.GetExternalLoginInfoAsync();
  36. if (info == null)
  37. {
  38. ModelState.AddModelError(string.Empty, "加载外部登录信息出错。");
  39. return View("Login", loginViewModel);
  40. }
  41. //如果用户之前已经登录过了,会在 AspNetUserLogins 表有对应的记录,这个时候无需创建新的记录,直接使用当前记录登录系统即可。
  42. var signInResult = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey,
  43. isPersistent: false, bypassTwoFactor: true);
  44. if (signInResult.Succeeded)
  45. {
  46. return LocalRedirect(returnUrl);
  47. }
  48. // 如果 AspNetUserLogins 表中没有记录,则代表用户没有一个本地帐户,这个时候我们就需要创建一个记录了。
  49. var email = info.Principal.FindFirstValue(ClaimTypes.Email);
  50. if (email != null)
  51. {
  52. // 通过邮箱地址去查询用户是否已存在
  53. var user = await _userManager.FindByEmailAsync(email);
  54. if (user == null)
  55. {
  56. user = new ApplicationUser
  57. {
  58. UserName = info.Principal.FindFirstValue(ClaimTypes.Email),
  59. Email = info.Principal.FindFirstValue(ClaimTypes.Email)
  60. };
  61. // 如果不存在,则创建一个用户,但是这个用户没有密码。
  62. await _userManager.CreateAsync(user);
  63. }
  64. // 在 AspNetUserLogins 表中,添加一行用户数据,然后将当前用户登录到系统中
  65. await _userManager.AddLoginAsync(user, info);
  66. await _signInManager.SignInAsync(user, isPersistent: false);
  67. return LocalRedirect(returnUrl);
  68. }
  69. // 如果我们获取不到电子邮件地址,我们需要将请求重定向到错误视图中。
  70. ViewBag.ErrorTitle = $"我们无法从提供商:{info.LoginProvider}中解析到您的邮件地址 ";
  71. ViewBag.ErrorMessage = "请通过联系 ltm@ddxc.org 寻求技术支持。";
  72. return View("Error");
  73. }
  74. #endregion

解决“我们无法完成你的请求”

image.png
遇到这个奇葩问题,是因为 ClientId 设置错误。
image.png
首先,ClientSecret 确实是 证书和密码 - 客户端密码 里面的 值。但我们需要的 ClientId 不是此处的 ID,而是概述界面中的 应用程序(客户端)ID。
image.png