Angular调用.NET Core SAML接口对接Azure AD的CORS及认证异常问题
初始CORS问题
我们正尝试为接收Angular应用请求的.NET Core API实现SAML认证,使用.NET 6环境下的Sustainsys.Saml2.AspNetCore2(2.9.2版本)包。通过浏览器直接访问API时,认证与授权功能正常。
但当Angular应用(运行于localhost:4200)连接后端.NET Core API(localhost:7085)并执行认证时,出现CORS错误:请求资源(Azure AD)缺少Access-Control-Allow-Origin头。我们发现调用身份提供商时,OPTIONS请求头中的Origin字段被设为null,身份提供商为Azure AD(托管于https://login.microsoftonline.com)。
我们认为这是由于前端与后端跨域,后端返回挑战重定向至Azure AD时,浏览器的同源策略导致Origin设为null,进而引发CORS问题。由于浏览器的同源策略限制,无法让Angular等前端JS应用直接允许跨域请求,除非服务器(此处为微软服务器)添加Access-Control-Allow-Origin头。
.NET SAML配置代码
public static AuthenticationBuilder AddSaml2Authentication(this IServiceCollection services, IConfiguration configuration) { var saml2Configuration = new Saml2Configurations(); configuration.Bind(ConfigurationKey, saml2Configuration); return services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = Saml2Defaults.Scheme; }) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, (options) => { options.LoginPath = GetLoginPath(configuration); options.ReturnUrlParameter = "redirectUrl"; }) .AddSaml2(options => { options.SPOptions.EntityId = new EntityId(saml2Configuration.SPEntityId); options.IdentityProviders.Add(new IdentityProvider( new EntityId(saml2Configuration.IdPEntityId), options.SPOptions) { MetadataLocation = saml2Configuration.IdPMetadataLocation }); }); }
认证控制器代码
public class AccountController : Controller { [AllowAnonymous] [HttpGet("Login")] public IActionResult Login(string redirectUrl) { string? redirectUri = Url.Action(nameof(LoginCallback), new { redirectUrl }); var properties = new AuthenticationProperties() { RedirectUri = redirectUri }; ChallengeResult result = Challenge(properties, Saml2Defaults.Scheme); return result; } [AllowAnonymous] [HttpGet("Callback")] public async Task<IActionResult> LoginCallback(string redirectUrl) { AuthenticateResult authenticateResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme); if (!authenticateResult.Succeeded) { return Unauthorized(); } IEnumerable<Claim>? claimCollection = authenticateResult.Principal?.Claims; if (claimCollection == null || !claimCollection.Any()) { return Problem(statusCode: StatusCodes.Status400BadRequest); } if (!string.IsNullOrEmpty(redirectUrl)) { return Redirect(redirectUrl); } return Ok(); } }
更新后的InResponseTo错误
我们尝试将SAML登录URL返回给前端并由其发起GET请求,成功在Azure门户完成登录,但点击“SignIn”按钮后出现新错误:
处理请求时发生未处理的异常。
UnexpectedInResponseToException: 收到的消息_5d94ac02-98e3-4219-800e-8f389c747b3c包含意外的InResponseTo值"idcd3e9548a6184c7aa0e2c3a061b107f9"。未找到保存请求状态的Cookie,因此该消息不应包含InResponseTo属性。此错误通常发生在SP发起登录时设置的Cookie丢失的情况下。
解决初始CORS问题
SAML认证是基于浏览器重定向的流程,不适合通过前端AJAX请求直接触发。正确的做法是让前端引导用户跳转到后端的登录端点,而非AJAX调用:
- 前端调整:当需要认证时,直接通过浏览器跳转至后端
/Login接口(例如window.location.href = 'https://localhost:7085/Login?redirectUrl=https://localhost:4200/目标页面'),整个认证流程在浏览器中完成,不会触发CORS检查。 - 后端CORS配置:确保后端已正确配置CORS允许前端域名的请求,但注意登录流程本身依赖重定向,无需AJAX跨域调用。
解决InResponseTo错误
该错误核心是SP发起登录时生成的状态Cookie丢失,导致回调时无法匹配响应的InResponseTo值,需从Cookie跨域配置和流程逻辑调整:
配置Cookie跨域属性
在Cookie认证和Sustainsys.Saml2配置中,设置Cookie的SameSite为None,并开启Secure(生产环境):.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, (options) => { options.LoginPath = GetLoginPath(configuration); options.ReturnUrlParameter = "redirectUrl"; options.Cookie.SameSite = SameSiteMode.None; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // 开发环境可改为CookieSecurePolicy.None options.Cookie.HttpOnly = true; }) .AddSaml2(options => { // 原有配置... options.SPOptions.SystemCookieSameSite = SameSiteMode.None; })调整前端跳转逻辑
不要通过AJAX获取登录URL再跳转,直接让用户浏览器导航到后端登录端点,确保Cookie正确绑定到后端域名,回调时可正常读取。验证回调流程
确保回调URL属于后端域名,认证完成后再重定向回前端页面,保证整个流程的Cookie都在后端域名下,避免丢失。
SAML认证是基于浏览器会话的重定向流程,不能用AJAX跨域调用触发。正确流程为:
- 前端检测到未认证时,引导用户跳转到后端
/Login接口,并携带前端回调地址。 - 后端发起SAML挑战,重定向到Azure AD登录。
- Azure AD登录完成后,回调到后端
/Callback接口,完成认证并设置Cookie。 - 后端重定向回前端指定页面,此时前端可通过AJAX调用后端API,浏览器会自动携带认证Cookie。
内容的提问来源于stack exchange,提问作者Hugo Mata

