.NET 4.8+NopCommerce3.8集成Azure AD认证遇nonce缺失错误
解决Azure AD认证中nonce字段缺失导致的AADSTS90014错误
问题描述
在基于NopCommerce 3.8框架的.NET 4.8项目中实现Azure AD认证时,回调环节触发以下错误:
OpenIdConnectMessage.Error不为空,表明存在错误。
错误: 'invalid_request'。错误描述: 'AADSTS90014: 凭证中缺少必填字段'nonce'。请确保登录请求包含所有必要参数。
跟踪ID: ef6427f0b00
关联ID: 0311700e-cadc-506e764
时间戳: 2024-08-29 03:51:08Z'。
错误URI: 'https://login.microsoftonline.com/error?code=90014'
问题原因
- 代码中手动设置
ProtocolValidator.RequireNonce = false,但Azure AD v2.0端点在使用response_type=id_token时强制要求nonce参数(用于防止重放攻击),禁用nonce直接导致请求不符合Azure AD的校验规则。 - 回调方法中错误使用
ExternalCookie作为认证类型获取结果,而Owin OpenIdConnect中间件默认将认证票据存储在CookieAuthenticationDefaults.AuthenticationType对应的Cookie中。
修复步骤
1. 恢复nonce自动处理
删除OpenIdConnectAuthenticationOptions中禁用nonce的配置,让中间件自动生成、存储和验证nonce:
// 移除以下代码块 // ProtocolValidator = new OpenIdConnectProtocolValidator // { // RequireNonce = false // },
2. 修正回调方法的认证类型
在ExternalLoginCallback中使用默认认证类型获取结果:
// 替换原代码中的认证类型 var authResult = HttpContext.GetOwinContext().Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType).Result;
3. 修正配置项映射(可选)
确保PostLogoutRedirectUri使用Web.config中对应的配置项,避免和RedirectUri混淆:
PostLogoutRedirectUri = ConfigurationManager.AppSettings["PostLogoutRedirectUri"],
4. 优化Challenge调用(可选)
Login方法中无需手动指定RedirectUri,中间件会自动使用OpenIdConnectAuthenticationOptions中配置的地址:
HttpContext.GetOwinContext().Authentication.Challenge( new AuthenticationProperties(), OpenIdConnectAuthenticationDefaults.AuthenticationType);
修改后的完整代码示例
OwinStartup.cs
using Microsoft.IdentityModel.Clients.ActiveDirectory; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; using Microsoft.Owin; using Microsoft.Owin.Host.SystemWeb; using Microsoft.Owin.Security; using Microsoft.Owin.Security.Cookies; using Microsoft.Owin.Security.OpenIdConnect; using Owin; using System; using System.Configuration; using System.Security.Claims; using System.Threading.Tasks; using System.Web; using System.Web.Helpers; [assembly: OwinStartup(typeof(Nop.Web.OwinStartup))] namespace Nop.Web { public class OwinStartup { public void Configuration(IAppBuilder app) { app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType); app.UseCookieAuthentication(new CookieAuthenticationOptions() { CookieSameSite = Microsoft.Owin.SameSiteMode.None, CookieSecure = CookieSecureOption.Always, CookieHttpOnly = true, CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebCookieManager() }); AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier; var openIdOptions = new OpenIdConnectAuthenticationOptions { ClientId = ConfigurationManager.AppSettings["ClientId"], Authority = ConfigurationManager.AppSettings["Authority"], PostLogoutRedirectUri = ConfigurationManager.AppSettings["PostLogoutRedirectUri"], RedirectUri = ConfigurationManager.AppSettings["RedirectUri"], Scope = "openid email profile offline_access", ClientSecret = ConfigurationManager.AppSettings["ClientSecret"], ResponseType = OpenIdConnectResponseType.IdToken, TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, }, Notifications = new OpenIdConnectAuthenticationNotifications { AuthorizationCodeReceived = async (context) => { var code = context.Code; string signedInUserID = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value; }, AuthenticationFailed = (context) => { context.HandleResponse(); context.Response.Redirect("/Error?message=" + context.Exception.Message); return Task.FromResult(0); } } }; app.UseOpenIdConnectAuthentication(openIdOptions); } } }
CustomerController.cs(回调方法)
[AllowAnonymous] [Route("signin-oidc")] public ActionResult ExternalLoginCallback() { var authResult = HttpContext.GetOwinContext().Authentication.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType).Result; if (authResult != null) { var userName = authResult.Identity.FindFirst(ClaimTypes.Name)?.Value; var email = authResult.Identity.FindFirst(ClaimTypes.Email)?.Value; var identity = new ClaimsIdentity(authResult.Identity.Claims, "ApplicationCookie"); HttpContext.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = true }, identity); return RedirectToAction("Index", "Home"); } return RedirectToAction("Login", "Customer"); }
内容的提问来源于stack exchange,提问作者Isanka Thalagala
相关产品推荐
相关产品推荐

