如何实现Asp.Net与Asp.Net Core应用基于Identity Server4共享access_token?
解决ASP.NET Core与经典ASP.NET(WebForms/MVC)间的Access Token共享问题
这个场景在混合新旧技术栈的项目里太常见了——虽然Data Protection帮我们搞定了跨应用的Cookie身份共享,但Access Token的存储和获取逻辑在两类框架里差异不小,导致Core登录后WebForms拿不到Token。下面给你几个实用的解决方案,按推荐程度排序:
方案1:让经典ASP.NET直接集成OpenID Connect(最标准的做法)
如果你的经典ASP.NET项目可以引入Owin中间件(大部分MVC/WebForms项目都支持),这是最靠谱的方案:让WebForms应用自己和Identity Server 4对接,走完整的OAuth2授权流程,这样它会自己获取并存储Access Token,同时因为共享了Cookie,用户在Core应用登录后访问WebForms会自动完成单点登录(SSO)。
经典ASP.NET(Owin)配置示例:
在项目的Startup.cs里添加如下配置:
public void Configuration(IAppBuilder app) { // 配置共享身份Cookie(和Core用同一个名称、域名) app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = "Cookies", CookieName = ".SharedAuthCookie", CookieHttpOnly = true, CookieSecure = CookieSecureOption.Always, // 生产环境必须开启HTTPS CookieSameSite = SameSiteMode.Lax, CookieDomain = ".your-domain.com" // 统一主域名实现跨子域共享 }); // 对接Identity Server的OpenID Connect app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions { Authority = "https://your-identity-server-url", ClientId = "webforms-app-client-id", ClientSecret = "webforms-app-client-secret", ResponseType = "code", Scope = "openid profile your-api-scope", RedirectUri = "https://your-webforms-app/signin-oidc", PostLogoutRedirectUri = "https://your-webforms-app/signout-callback-oidc", SaveTokens = true, // 自动将Token存入共享Cookie UseTokenLifetime = false }); }
之后在WebForms页面或MVC控制器里,就能直接从身份上下文获取Token:
// WebForms页面 protected void Page_Load(object sender, EventArgs e) { var accessToken = Context.GetOwinContext().Authentication.User.FindFirst("access_token")?.Value; if (!string.IsNullOrEmpty(accessToken)) { // 用Token调用内嵌API using (var client = new HttpClient()) { client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var response = client.GetAsync("https://your-webforms-app/api/protected-endpoint").Result; // 处理响应逻辑 } } }
方案2:将Access Token写入共享加密Cookie(兼容老项目)
如果WebForms项目无法集成Owin,那可以利用已有的Data Protection共享机制,在ASP.NET Core登录成功后,把Access Token加密后写入共享Cookie,让WebForms读取并解密使用。
ASP.NET Core端:登录后写入Token到共享Cookie
在Core项目的OpenID Connect事件中,拿到Token后加密写入Cookie:
builder.Services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(options => { options.Cookie.Name = ".SharedAuthCookie"; options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.SameSite = SameSiteMode.Lax; options.Cookie.Domain = ".your-domain.com"; }) .AddOpenIdConnect(options => { options.Authority = "https://your-identity-server-url"; options.ClientId = "core-app-client-id"; options.ClientSecret = "core-app-client-secret"; options.ResponseType = "code"; options.SaveTokens = true; options.Events = new OpenIdConnectEvents { OnTokenValidated = async context => { var accessToken = context.SecurityToken as JwtSecurityToken; if (accessToken != null) { var refreshToken = context.Properties.GetTokenValue("refresh_token"); // 用Data Protection加密Token var dataProtector = context.HttpContext.RequestServices .GetRequiredService<IDataProtectionProvider>() .CreateProtector("SharedTokenProtectionKey"); var encryptedAccessToken = dataProtector.Protect(accessToken.RawData); var encryptedRefreshToken = dataProtector.Protect(refreshToken); // 写入共享Cookie context.Response.Cookies.Append("SharedAccessToken", encryptedAccessToken, new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Lax, Expires = accessToken.ValidTo, Path = "/", Domain = ".your-domain.com" }); } await Task.CompletedTask; } }; });
经典ASP.NET端:读取并解密Token
先在Global.asax中配置Data Protection(和Core共享密钥存储):
protected void Application_Start() { // 共享Core的Data Protection密钥 var dataProtectionProvider = DataProtectionProvider.Create( new DirectoryInfo(@"\\shared-server\dataprotection-keys"), // 和Core用同一个密钥目录 config => config.SetApplicationName("YourSharedAppName")); Application["DataProtectionProvider"] = dataProtectionProvider; }
然后在WebForms页面中读取解密:
protected void Page_Load(object sender, EventArgs e) { var dataProtectionProvider = (IDataProtectionProvider)Application["DataProtectionProvider"]; var dataProtector = dataProtectionProvider.CreateProtector("SharedTokenProtectionKey"); var tokenCookie = Request.Cookies["SharedAccessToken"]; if (tokenCookie != null) { try { var accessToken = dataProtector.Unprotect(tokenCookie.Value); // 用Token调用API逻辑 } catch (CryptographicException) { // 解密失败(密钥不匹配/Token过期),跳转登录页 Response.Redirect("https://your-identity-server-url/account/login?returnUrl=" + Request.Url); } } else { // 无Token,跳转登录 Response.Redirect("https://your-identity-server-url/account/login?returnUrl=" + Request.Url); } }
关键安全注意事项
- 必须用HTTPS:所有Cookie都要开启
Secure属性,防止Token被明文传输。 - HttpOnly Cookie:Token相关Cookie必须设置
HttpOnly,避免XSS攻击窃取。 - Token过期同步:Cookie的过期时间要和Access Token的有效期保持一致,避免无效Token残留。
- 密钥管理:Data Protection的密钥要存储在共享安全位置(如文件服务器、Azure Key Vault),不要硬编码在项目中。
内容的提问来源于stack exchange,提问作者Jeff Keslinke
相关产品推荐
相关产品推荐

