Azure托管ASP.NET MVC应用实现SSO单点登录故障排查
问题定位
你遇到的SSO失效由两个核心问题共同导致:
- 本地开发环境有
C:\ApplicationData目录的读写权限,数据保护密钥可以正常持久化,两个应用读取同一份密钥实现Cookie互信;但Azure App Service运行在沙箱环境中,默认无C盘非系统目录的读写权限,会抛出Access Denied错误,两个应用各自生成独立的临时密钥,无法解密对方签发的认证Cookie。 - 你配置的
CookieDomain = ".azurewebsites.net"在生产环境完全无效:azurewebsites.net属于公共后缀列表(Public Suffix List)收录的域名,现代浏览器(Chrome/Edge/Firefox等)会主动拒绝给该根域设置跨子域Cookie,根本不会在两个应用的域名间传递认证Cookie。
另外你现有配置还有一处逻辑不一致:Cookie认证选项里配置的过期时间是120分钟,但登录逻辑里手动设置的Cookie过期时间是30分钟,会导致实际Cookie有效期和预期不符,不影响SSO生效但建议统一。
修复方案
按以下步骤调整配置即可实现Azure环境下的跨应用SSO:
1. 替换密钥存储方案(必须)
不要使用本地磁盘路径存储数据保护密钥,生产环境推荐使用Azure Blob Storage持久化密钥环、Azure Key Vault加密密钥,两个应用共用同一份存储和密钥即可实现Cookie互信。
- 首先为两个应用安装所需NuGet包:
Install-Package Microsoft.Owin.Security.Interop Install-Package Microsoft.AspNetCore.DataProtection.AzureStorage Install-Package Microsoft.AspNetCore.DataProtection.AzureKeyVault Install-Package Microsoft.Azure.Services.AppAuthentication
- 提前准备资源:
- 新建一个Azure Storage账户,在其中创建一个私有Blob容器用于存储密钥文件
- 新建Azure Key Vault,为两个App Service开启系统托管标识,并授予托管标识对Key Vault中密钥的
Get、Wrap、Unwrap权限,对Storage账户Blob容器的Storage Blob Data Contributor权限
- 替换
Startup.Auth.cs中原有DataProtection初始化逻辑,修正后的完整Cookie认证配置如下:
public void ConfigureAuth(IAppBuilder app) { app.CreatePerOwinContext(ApplicationDbContext.Create); app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create); // 初始化共享数据保护提供器 var blobUri = new Uri("https://<你的存储账户名>.blob.core.windows.net/<密钥容器名>/auth-keys.xml"); var keyVaultKeyIdentifier = "https://<你的Key Vault名称>.vault.azure.net/keys/<加密密钥名>/"; var tokenProvider = new AzureServiceTokenProvider(); var dataProtectionProvider = DataProtectionProvider.Create( new AzureBlobStorageRepository(new CloudBlockBlob(blobUri)), builder => { builder.SetApplicationName("SharedCookieApp"); // 测试环境可删除下一行,仅用Blob存储密钥即可运行,生产环境建议保留 builder.ProtectKeysWithAzureKeyVault( new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback)), keyVaultKeyIdentifier); } ); app.UseCookieAuthentication(new CookieAuthenticationOptions() { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, CookieName = ".AspNet.SharedCookie", CookieDomain = ".<你的自定义根域>", SlidingExpiration = true, ExpireTimeSpan = TimeSpan.FromMinutes(120), LoginPath = PathString.FromUriComponent("/Account/Login"), LogoutPath = PathString.FromUriComponent("/Account/Logout"), TicketDataFormat = new AspNetTicketDataFormat( new DataProtectorShim( dataProtectionProvider.CreateProtector( "Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", "Cookies.Application", "v2" ) ) ), CookieManager = new ChunkingCookieManager(), CookieSecure = CookieSecureOption.Always // 生产环境强制HTTPS传输Cookie,避免被窃听 }); }
注意:两个应用的上述配置必须完全一致,包括ApplicationName、CreateProtector传入的字符串序列、Cookie名称、域名等所有参数,任意参数不一致都会导致解密失败。
2. 配置自定义域名(必须)
由于.azurewebsites.net的公共后缀限制,你必须为两个应用绑定同一个根域下的自定义子域名,例如app1.yourdomain.com、app2.yourdomain.com,将配置中的CookieDomain值改为.yourdomain.com,同时为两个自定义域名开启HTTPS。配置完成后浏览器才会正常在两个子域间传递认证Cookie。
3. 统一登录逻辑的Cookie过期时间
将登录逻辑中手动设置的ExpiresUtc和Cookie认证配置中的ExpireTimeSpan保持一致,避免出现Cookie提前过期的问题:
authenticationManager.SignIn(new AuthenticationProperties() { AllowRefresh = true, IsPersistent = true, ExpiresUtc = DateTime.UtcNow.AddMinutes(120), IssuedUtc = DateTime.UtcNow }, id);
验证步骤
发布应用后按以下顺序验证:
- 访问第一个应用完成登录,打开浏览器开发者工具-应用-Cookies,确认存在
.AspNet.SharedCookieCookie,域名为你配置的自定义根域,没有关联azurewebsites.net域名 - 直接访问第二个应用的需要登录的页面,确认无需跳转登录页即可正常访问,请求头中自动携带了共享认证Cookie
- 若需要单点登出能力,可在任意一个应用的退出逻辑中,调用另一个应用的登出接口清除Cookie即可。
内容的提问来源于stack exchange,提问作者Kiran_92
相关产品推荐
相关产品推荐

