.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所需基础用户信息权限)
- 重定向URI:例如
步骤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.OpenIdConnectNuGet包
内容的提问来源于stack exchange,提问作者Guilherme Serafina
相关产品推荐
相关产品推荐

