You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

外部客户能否通过Microsoft Entra用户名密码登录?.NET Core实现咨询

可行方案:Authorization Code Flow + 自定义登录UI(适配Microsoft Entra External Identities)

ROPC流程的限制(不支持MFA、仅兼容本地账户、安全风险高)确实难以接受,Authorization Code Flow是合规的替代方案——既支持自定义登录表单,又兼容Entra External Identities的多账户类型(本地、社交、企业),同时支持MFA等安全特性。


实现步骤

1. 租户侧配置

  • 在Entra门户中注册Web应用,选择Web/API类型,启用Authorization Code Flow(勾选"授权码"选项)。
  • 设置重定向URI:例如https://localhost:5001/signin-oidc(本地开发环境)。
  • 记录租户ID、客户端ID、客户端密钥(机密客户端模式)。

2. .NET Core 项目配置

2.1 安装依赖包

Install-Package Microsoft.Identity.Web
Install-Package Microsoft.Identity.Web.UI

2.2 配置appsettings.json

"AzureAd": {
  "Instance": "https://login.microsoftonline.com/",
  "TenantId": "你的租户ID",
  "ClientId": "你的客户端ID",
  "ClientSecret": "你的客户端密钥",
  "CallbackPath": "/signin-oidc",
  "SignedOutCallbackPath": "/signout-callback-oidc"
}

2.3 配置认证服务(Program.cs/.NET 6+)

var builder = WebApplication.CreateBuilder(args);

// 添加Microsoft Identity Web认证服务
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(options =>
    {
        builder.Configuration.Bind("AzureAd", options);
        // 拦截默认登录跳转,引导至自定义表单
        options.Events = new OpenIdConnectEvents
        {
            OnRedirectToIdentityProvider = context =>
            {
                if (context.Properties.Items.TryGetValue("LoginPath", out _) || 
                    context.Request.Path == "/Account/Login")
                {
                    context.Response.Redirect($"/Account/Login?returnUrl={Uri.EscapeDataString(context.Properties.RedirectUri ?? "/")}");
                    context.HandleResponse();
                }
                return Task.CompletedTask;
            }
        };
    })
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

// 设置Cookie认证的登录路径,引导未授权用户到自定义表单
builder.Services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = "/Account/Login";
});

builder.Services.AddControllersWithViews()
    .AddMicrosoftIdentityUI();

var app = builder.Build();

// 中间件配置
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

3. 自定义登录表单实现

3.1 创建登录视图(Views/Account/Login.cshtml)

@{
    ViewData["Title"] = "自定义登录";
}
<div class="container">
    <h2>@ViewData["Title"]</h2>
    <form asp-action="Login" method="post" class="mt-4">
        <input type="hidden" asp-for="ReturnUrl" />
        <div class="mb-3">
            <label asp-for="Username" class="form-label"></label>
            <input asp-for="Username" class="form-control" placeholder="输入用户名或邮箱" />
            <span asp-validation-for="Username" class="text-danger"></span>
        </div>
        <div class="mb-3">
            <label asp-for="Password" class="form-label"></label>
            <input asp-for="Password" type="password" class="form-control" placeholder="输入密码" />
            <span asp-validation-for="Password" class="text-danger"></span>
        </div>
        <button type="submit" class="btn btn-primary">登录</button>
        <!-- 可选:添加社交/企业登录按钮 -->
        <a asp-action="ExternalLogin" asp-route-provider="Google" class="btn btn-secondary ms-2">用Google登录</a>
    </form>
</div>

3.2 创建登录模型(Models/LoginViewModel.cs)

using System.ComponentModel.DataAnnotations;

public class LoginViewModel
{
    [Required(ErrorMessage = "请输入用户名或邮箱")]
    [Display(Name = "用户名/邮箱")]
    public string Username { get; set; }

    [Required(ErrorMessage = "请输入密码")]
    [DataType(DataType.Password)]
    [Display(Name = "密码")]
    public string Password { get; set; }

    public string ReturnUrl { get; set; }
}

3.3 创建Account控制器(Controllers/AccountController.cs)

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Mvc;
using YourApp.Models;

public class AccountController : Controller
{
    [HttpGet]
    public IActionResult Login(string returnUrl = null)
    {
        ViewData["ReturnUrl"] = returnUrl;
        return View(new LoginViewModel { ReturnUrl = returnUrl });
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Login(LoginViewModel model)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }

        // 构造认证属性,传递凭据到Entra端点
        var authProps = new AuthenticationProperties
        {
            RedirectUri = model.ReturnUrl ?? Url.Content("~/"),
            Items =
            {
                { OpenIdConnectDefaults.TokenRequestUsername, model.Username },
                { OpenIdConnectDefaults.TokenRequestPassword, model.Password }
            }
        };

        // 触发Entra认证流程,由Microsoft验证凭据
        return Challenge(authProps, OpenIdConnectDefaults.AuthenticationScheme);
    }

    // 可选:处理外部账户登录
    [HttpGet]
    public IActionResult ExternalLogin(string provider, string returnUrl = null)
    {
        var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
        var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
        return Challenge(properties, provider);
    }

    public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null)
    {
        var info = await HttpContext.AuthenticateAsync(OpenIdConnectDefaults.AuthenticationScheme);
        if (info.Succeeded)
        {
            return LocalRedirect(returnUrl ?? "/");
        }
        return RedirectToAction("Login");
    }
}

4. 关键说明

  • 此方案基于Authorization Code Flow,安全性远高于ROPC,支持MFA、社交/企业账户登录,完全规避ROPC的限制。
  • 自定义表单收集的凭据会直接传递给Entra的认证端点,由Microsoft负责验证,无需自行存储或处理密码,降低安全风险。
  • 若需支持外部账户登录,可在表单中添加对应按钮,触发ExternalLogin方法跳转至第三方认证页面。

内容的提问来源于stack exchange,提问作者akakarikos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 23:52:04