ASP.NET Core 3.1站点iOS Safari访问[Authorize]路由间歇性403错误如何解决?
问题根因
该偶发403错误由ASP.NET Core默认的Cookie SameSite策略与Safari浏览器的兼容性问题导致:
- ASP.NET Core 3.0及以上版本默认将Cookie SameSite属性设为Lax,部分iOS Safari、旧版桌面Safari对SameSite规则的实现存在缺陷,会意外拦截合法的身份验证Cookie、防伪令牌Cookie
- Cookie被拦截后,服务端无法识别用户身份,访问需要授权的路由直接返回403,若匿名页面包含防伪表单,防伪校验失败也会返回403
- 清理网站数据后Cookie会重新下发,所以临时恢复正常,待浏览器再次触发SameSite校验逻辑后问题复现
修复步骤
1. 新增Cookie策略配置与兼容性处理
在Startup.cs中按如下规则调整代码:
public class Startup { // 原有构造函数、属性保持不变 public void ConfigureServices(IServiceCollection services) { // 新增Cookie策略配置,放在AddAuthentication之前 services.Configure<CookiePolicyOptions>(options => { options.MinimumSameSitePolicy = SameSiteMode.Unspecified; options.OnAppendCookie = cookieContext => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); options.OnDeleteCookie = cookieContext => CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); }); // 原有AddAuthentication、AddCookie逻辑调整 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, o => { // 原有配置保持不变 o.ExpireTimeSpan = TimeSpan.FromDays(90); o.AccessDeniedPath = "/Samadhi/SignIn/"; o.LoginPath = "/Samadhi/SignIn/"; o.LogoutPath = "/Samadhi/SignOut/"; o.SlidingExpiration = true; // 新增:开启滑动过期,用户活跃时自动续期Cookie // 新增Cookie属性配置 o.Cookie.HttpOnly = true; o.Cookie.SecurePolicy = CookieSecurePolicy.Always; o.Cookie.SameSite = SameSiteMode.Unspecified; // 原有OnRedirectToLogin逻辑保持不变 var defaultCallback = o.Events.OnRedirectToLogin; o.Events.OnRedirectToLogin = context => { if (context.Request.Path.StartsWithSegments(new PathString("/samadhi"), StringComparison.OrdinalIgnoreCase)) { context.RedirectUri = "/Samadhi/SignIn/"; context.Response.Redirect(context.RedirectUri); } else if (context.Request.Path.StartsWithSegments(new PathString("/rbk"), StringComparison.OrdinalIgnoreCase)) { context.RedirectUri = "/Rbk/SignIn/"; context.Response.Redirect(context.RedirectUri); } return defaultCallback(context); }; o.Validate(); }); // 原有防伪服务配置补充SameSite设置 services.AddAntiforgery(options => { options.Cookie.SameSite = SameSiteMode.Unspecified; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.HttpOnly = true; }); // 剩余原有配置(Identity、设备检测、验证码、MVC配置等)保持不变 } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 原有环境判断、Https跳转逻辑保持不变 if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); // 新增:Cookie策略中间件,必须放在UseAuthentication之前 app.UseCookiePolicy(); // 原有中间件顺序保持不变 app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); // 原有端点配置保持不变 app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } // 新增:SameSite兼容性判断方法 private static void CheckSameSite(HttpContext httpContext, CookieOptions options) { if (options.SameSite == SameSiteMode.None) { var userAgent = httpContext.Request.Headers["User-Agent"].ToString(); // 匹配所有不支持SameSite=None的Safari及旧版浏览器 if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12") || (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") && userAgent.Contains("Version/") && userAgent.Contains("Safari")) || userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6")) { options.SameSite = SameSiteMode.Unspecified; } } } }
2. 冗余代码清理(可选,不影响功能)
SignIn方法中存在冗余的ClaimsIdentity创建逻辑,可以调整为直接复用第一次创建的实例:
// 原有创建identity逻辑保持不变 var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); // 删掉重复创建ClaimsIdentity的逻辑,直接使用已创建的identity ClaimsPrincipal principal = new ClaimsPrincipal(identity);
验证方式
部署后可以通过Safari开发者工具的「网络」面板查看请求的Cookie携带情况,「存储」面板查看站点Cookie的SameSite属性是否正常,确认没有被浏览器标记为拦截。
内容的提问来源于stack exchange,提问作者Ross Kelly
相关产品推荐
相关产品推荐

