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

.NET 4.7.2无会话Web应用添加Azure AD认证方案咨询

.NET 4.7.2 无Session应用添加Azure AD认证方案(兼容现有本地认证)

核心思路

你的应用禁用了Session(<sessionState mode="Off"/>),无法直接使用依赖Session的Owin中间件,因此采用手动实现OAuth2授权码流程,将Azure AD认证后的用户信息映射到现有系统的认证体系,生成应用自身的JWT令牌,完全兼容现有Cookie校验逻辑,无需大幅改动原有代码。

步骤1:Azure AD应用注册

  • 在Azure门户完成应用注册,配置以下关键项:
    • 重定向URI:例如https://你的域名/api/azuread/callback
    • 记录客户端ID、租户ID、客户端密钥(建议用证书替代密钥提升安全性)
    • 添加API权限:勾选User.Read(OpenID Connect所需基础用户信息权限)

步骤2:添加「使用Microsoft登录」跳转逻辑

在登录页面添加跳转按钮,后端提供生成Azure AD授权URL的接口:

[AllowAnonymous]
[HttpGet]
[Route("azuread/login")]
public IHttpActionResult AzureAdLogin()
{
    // 生成CSRF防护的state参数
    string state = Guid.NewGuid().ToString("N");
    
    // 将state存入HttpOnly Cookie(无Session时用Cookie暂存)
    var stateCookie = new HttpCookie("AzureAdState", state)
    {
        HttpOnly = true,
        Secure = Request.RequestUri.Scheme == Uri.UriSchemeHttps,
        SameSite = SameSiteMode.Lax,
        Expires = DateTime.UtcNow.AddMinutes(10) // 授权流程有效期10分钟
    };
    HttpContext.Current.Response.Cookies.Add(stateCookie);

    // 构造Azure AD授权URL
    string tenantId = ConfigurationManager.AppSettings["AzureAd:TenantId"];
    string clientId = ConfigurationManager.AppSettings["AzureAd:ClientId"];
    string redirectUri = ConfigurationManager.AppSettings["AzureAd:RedirectUri"];
    
    string authUrl = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize?" +
        $"client_id={Uri.EscapeDataString(clientId)}" +
        $"&response_type=code" +
        $"&redirect_uri={Uri.EscapeDataString(redirectUri)}" +
        $"&scope={Uri.EscapeDataString("openid email profile offline_access")}" +
        $"&state={Uri.EscapeDataString(state)}" +
        $"&response_mode=query";

    return Redirect(authUrl);
}

步骤3:处理Azure AD回调并生成应用令牌

创建回调端点,接收授权码并完成认证流程:

[AllowAnonymous]
[HttpGet]
[Route("azuread/callback")]
public async Task<IHttpActionResult> AzureAdCallback(string code, string state)
{
    // 验证CSRF state
    var stateCookie = HttpContext.Current.Request.Cookies["AzureAdState"];
    if (stateCookie == null || stateCookie.Value != state)
    {
        return BadRequest("无效的请求状态");
    }
    // 验证后删除state Cookie
    stateCookie.Expires = DateTime.UtcNow.AddDays(-1);
    HttpContext.Current.Response.Cookies.Add(stateCookie);

    // 用授权码交换Azure AD令牌
    string tenantId = ConfigurationManager.AppSettings["AzureAd:TenantId"];
    string clientId = ConfigurationManager.AppSettings["AzureAd:ClientId"];
    string clientSecret = ConfigurationManager.AppSettings["AzureAd:ClientSecret"];
    string redirectUri = ConfigurationManager.AppSettings["AzureAd:RedirectUri"];

    using (var client = new HttpClient())
    {
        var tokenRequest = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            {"grant_type", "authorization_code"},
            {"code", code},
            {"client_id", clientId},
            {"client_secret", clientSecret},
            {"redirect_uri", redirectUri},
            {"scope", "openid email profile"}
        });

        var tokenResponse = await client.PostAsync($"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", tokenRequest);
        if (!tokenResponse.IsSuccessStatusCode)
        {
            return BadRequest("Azure AD令牌获取失败");
        }

        var tokenData = await tokenResponse.Content.ReadAsAsync<dynamic>();
        string idToken = tokenData.id_token;

        // 验证ID Token的合法性
        var validatedIdentity = ValidateAzureAdIdToken(idToken);
        if (validatedIdentity == null)
        {
            return Unauthorized();
        }

        // 将Azure AD用户映射到现有系统用户
        string userEmail = validatedIdentity.Claims.First(c => c.Type == "email").Value;
        var localUser = _userService.GetUserByEmail(userEmail); // 替换为你的用户查询逻辑
        if (localUser == null)
        {
            return BadRequest("该Microsoft账号未关联本地系统账号");
        }

        // 调用现有逻辑生成应用自身的JWT令牌并设置Cookie
        var token = _authenticate.CreateToken(localUser.Username, string.Empty, localUser.DomainName, false);
        HttpCookie authCookie = _authenticate.CreateCookie(token, false);
        HttpContext.Current.Response.Cookies.Add(authCookie);

        // 跳转回应用主页或原请求页面
        return Redirect("/");
    }
}

步骤4:Azure AD ID Token验证逻辑

通过System.IdentityModel.Tokens.Jwt库(NuGet安装)验证ID Token合法性:

private ClaimsIdentity ValidateAzureAdIdToken(string idToken)
{
    string tenantId = ConfigurationManager.AppSettings["AzureAd:TenantId"];
    string clientId = ConfigurationManager.AppSettings["AzureAd:ClientId"];

    var tokenValidationParameters = new TokenValidationParameters
    {
        ValidAudience = clientId,
        ValidIssuer = $"https://login.microsoftonline.com/{tenantId}/v2.0",
        ValidateIssuerSigningKey = true,
        IssuerSigningKeyResolver = (token, securityToken, kid, parameters) =>
        {
            // 获取Azure AD公钥用于签名验证
            var keysTask = new HttpClient().GetFromJsonAsync<JsonWebKeySet>($"https://login.microsoftonline.com/{tenantId}/discovery/v2.0/keys");
            keysTask.Wait();
            return keysTask.Result.Keys.Where(k => k.Kid == kid);
        },
        ValidateLifetime = true,
        ClockSkew = TimeSpan.FromMinutes(5) // 允许5分钟时钟偏差
    };

    try
    {
        var handler = new JwtSecurityTokenHandler();
        var claimsPrincipal = handler.ValidateToken(idToken, tokenValidationParameters, out _);
        return claimsPrincipal.Identity as ClaimsIdentity;
    }
    catch
    {
        return null;
    }
}

步骤5:Web.config配置

添加Azure AD相关配置项(建议加密<appSettings>节点):

<appSettings>
  <add key="AzureAd:TenantId" value="你的租户ID"/>
  <add key="AzureAd:ClientId" value="你的客户端ID"/>
  <add key="AzureAd:ClientSecret" value="你的客户端密钥"/>
  <add key="AzureAd:RedirectUri" value="https://你的域名/api/azuread/callback"/>
</appSettings>

关键注意事项

  • CSRF防护:必须验证state参数,防止跨站请求伪造,无Session时用Cookie暂存state
  • Cookie安全:所有认证相关Cookie需设置HttpOnly、Secure、SameSite属性
  • 用户映射:需实现Azure AD用户到本地系统用户的关联逻辑,例如通过邮箱匹配或自动创建
  • 令牌过期:Azure AD的ID Token默认有效期1小时,应用自身令牌保持原有过期策略即可
  • 依赖库:确保安装System.IdentityModel.Tokens.Jwt和Microsoft.IdentityModel.Protocols.OpenIdConnect NuGet包

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 00:22:51