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

.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'

问题原因

  1. 代码中手动设置ProtocolValidator.RequireNonce = false,但Azure AD v2.0端点在使用response_type=id_token时强制要求nonce参数(用于防止重放攻击),禁用nonce直接导致请求不符合Azure AD的校验规则。
  2. 回调方法中错误使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 05:39:59