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

如何为Blazor Server实现MSAL弹窗登录模式?

Blazor Server 实现 MSAL 弹窗登录方案

Blazor Server 没有像 Blazor WebAssembly 那样直接提供 LoginMode 配置项来切换弹窗/重定向登录,因为它的认证流程由服务器端主导。要实现弹窗登录,需要结合 MSAL.js 和 Blazor 的 JavaScript 互操作能力,以下是具体实现步骤:


1. 引入 MSAL.js 库

在 Pages/_Host.cshtml 的 <head> 或 <body> 末尾添加 MSAL.js 的 CDN 引用:

<script src="https://alcdn.msauth.net/browser/2.38.1/js/msal-browser.min.js"></script>

2. 编写弹窗登录的 JavaScript 逻辑

在 wwwroot/js 目录下创建 msalPopupAuth.js 文件,封装 MSAL 初始化和弹窗登录的逻辑:

let msalInstance;

// 初始化 MSAL 实例
export function initializeMsal(clientId, tenantId) {
    const msalConfig = {
        auth: {
            clientId: clientId,
            authority: `https://login.microsoftonline.com/${tenantId}`,
            redirectUri: window.location.origin // 弹窗登录的回调地址,默认当前站点根路径
        }
    };
    msalInstance = new msal.PublicClientApplication(msalConfig);
}

// 触发弹窗登录
export async function loginPopup() {
    const loginRequest = {
        scopes: ["user.read"] // 根据业务需求调整所需权限
    };
    try {
        const response = await msalInstance.loginPopup(loginRequest);
        return response.idToken; // 返回 ID Token 给 Blazor 后端验证
    } catch (error) {
        console.error("弹窗登录失败:", error);
        throw error;
    }
}

3. 在 Blazor 组件中调用弹窗登录

创建或修改登录组件(比如 Login.razor),通过 JS 互操作调用弹窗登录,并将 Token 发送到后端验证:

@inject IJSRuntime JSRuntime
@inject NavigationManager NavManager
@inject HttpClient Http

<button class="btn btn-primary" @onclick="TriggerPopupLogin">弹窗登录</button>

@code {
    private async Task TriggerPopupLogin() {
        try {
            // 初始化 MSAL(可移至组件初始化事件,避免重复调用)
            await JSRuntime.InvokeVoidAsync("initializeMsal", 
                "你的AzureAD客户端ID", 
                "你的AzureAD租户ID");
            
            // 调用弹窗登录,获取ID Token
            var idToken = await JSRuntime.InvokeAsync<string>("loginPopup");
            
            // 将Token发送到后端API验证并建立会话
            var validationResult = await Http.PostAsJsonAsync(
                "/api/auth/validate-token", 
                new { IdToken = idToken });
            
            if (validationResult.IsSuccessStatusCode) {
                // 登录成功,跳转至主页
                NavManager.NavigateTo("/");
            } else {
                // 处理Token验证失败逻辑
                Console.WriteLine("Token验证失败");
            }
        } catch (Exception ex) {
            // 处理登录异常(如弹窗被拦截)
            Console.WriteLine($"登录出错: {ex.Message}");
        }
    }
}

4. 后端实现 Token 验证与会话建立

创建 AuthController,负责验证前端传递的 ID Token,并为用户建立 Cookie 会话:

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;

[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    private readonly IConfiguration _config;

    public AuthController(IConfiguration config)
    {
        _config = config;
    }

    [HttpPost("validate-token")]
    public async Task<IActionResult> ValidateToken([FromBody] TokenRequest request)
    {
        var validationParams = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = $"https://login.microsoftonline.com/{_config["AzureAd:TenantId"]}/v2.0",
            ValidateAudience = true,
            ValidAudience = _config["AzureAd:ClientId"],
            ValidateLifetime = true,
            IssuerSigningKeys = await GetSigningKeysAsync()
        };

        try
        {
            var handler = new JwtSecurityTokenHandler();
            handler.ValidateToken(request.IdToken, validationParams, out var validatedToken);
            
            // 从Token中提取用户声明
            var jwtToken = (JwtSecurityToken)validatedToken;
            var claims = jwtToken.Claims.ToList();
            
            // 可选:添加自定义声明
            claims.Add(new Claim(ClaimTypes.Name, jwtToken.Subject));
            
            // 创建身份认证主体
            var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
            var principal = new ClaimsPrincipal(identity);
            
            // 建立Cookie会话
            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
            
            return Ok();
        }
        catch
        {
            return Unauthorized("Token验证失败");
        }
    }

    // 获取Azure AD的签名密钥
    private async Task<IEnumerable<SecurityKey>> GetSigningKeysAsync()
    {
        var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(
            $"https://login.microsoftonline.com/{_config["AzureAd:TenantId"]}/v2.0/.well-known/openid-configuration",
            new OpenIdConnectConfigurationRetriever());
        
        var openIdConfig = await configManager.GetConfigurationAsync(CancellationToken.None);
        return openIdConfig.SigningKeys;
    }

    // 接收前端Token的请求模型
    public class TokenRequest
    {
        public string IdToken { get; set; }
    }
}

5. 配置 Blazor Server 认证服务

在 Program.cs 中配置 Cookie 认证(作为默认认证方案),同时可以保留 OpenID Connect 配置作为 fallback:

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
    options.ClientId = builder.Configuration["AzureAd:ClientId"];
    options.ClientSecret = builder.Configuration["AzureAd:ClientSecret"];
    options.Authority = $"https://login.microsoftonline.com/{builder.Configuration["AzureAd:TenantId"]}/v2.0";
    options.ResponseType = "code";
    options.SaveTokens = true;
});

builder.Services.AddAuthorization();

注意事项

  • Azure AD 应用配置:确保在 Azure 门户的应用注册中,将当前站点的 URL 添加到「重定向 URI」列表(类型选「单页应用 (SPA)」)。
  • 弹窗拦截处理:需要提示用户允许浏览器弹窗,否则登录流程会失败。
  • MSAL 版本:生产环境建议使用最新版的 MSAL.js,可从官方文档获取最新 CDN 地址。
  • Token 缓存:可以在 JS 层实现 Token 缓存,避免重复登录。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 12:13:16