基于Owin Token实现跨服务MVC与WebAPI身份认证问题求助
你遇到的核心问题是:直接把MVC的.AspNet.ApplicationCookie值当作Bearer Token发送给WebAPI是行不通的。这个Cookie里存储的是Owin Cookie认证模块加密后的Cookie认证票据(Cookie Authentication Ticket),而WebAPI配置的OAuthBearerAuthenticationMiddleware是用来解析标准OAuth2 Bearer Token(比如JWT格式)的,两者的格式、加密逻辑完全不同——哪怕两边MachineKey一致,Bearer中间件也无法识别Cookie票据的格式。
下面给你三个不同方向的解决方案,你可以根据自己的需求选择:
方案1:让WebAPI直接支持Cookie认证(最简单的无缝方案)
既然MVC和WebAPI已经配置了相同的MachineKey,你可以直接在WebAPI中启用Cookie认证中间件,这样MVC的认证Cookie就能被WebAPI直接识别,不需要手动处理Bearer请求头。如果你的核心需求是“无缝验证已认证请求”,这是最省事的选择:
修改WebAPI的启动配置代码:
// 先添加Cookie认证,参数必须和MVC完全一致 app.UseCookieAuthentication(new CookieAuthenticationOptions() { AuthenticationType = "你的Auth_type值", // 要和MVC里的Auth_type完全匹配 CookieName = ".AspNet.ApplicationCookie", // 和MVC的Cookie名称保持一致 CookieSecure = CookieSecureOption.Never, CookieHttpOnly = false, AuthenticationMode = AuthenticationMode.Active // 主动验证Cookie中的身份信息 }); // 保留原有的Bearer认证,兼容两种认证方式 var OAuthOptions = new OAuthBearerAuthenticationOptions { AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive, AuthenticationType = OAuthDefaults.AuthenticationType, }; app.UseOAuthBearerAuthentication(OAuthOptions);
配置完成后,当MVC的前端带着认证Cookie请求WebAPI时,API会自动识别并验证身份,不需要额外修改前端代码。
方案2:生成标准JWT Token传给WebAPI(规范的跨服务认证方案)
如果必须使用Bearer请求头的方式,推荐从MVC的已认证用户Claims生成标准的JWT Token,然后WebAPI用JWT认证中间件验证。这种方式更符合现代跨服务认证的规范,也更容易扩展到其他服务:
步骤1:在MVC项目中添加JWT生成逻辑
首先安装System.IdentityModel.Tokens.Jwt包,然后在登录成功后生成JWT并传递给前端:
// 登录成功后(比如SignIn之后) var claimsIdentity = User.Identity as ClaimsIdentity; // 生成JWT需要的密钥,两边(MVC和API)必须一致 var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("你的共享密钥,比如一个随机的长字符串")); var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); // 构建JWT Token var jwtToken = new JwtSecurityToken( issuer: "https://your-mvc-domain.com", // MVC网站的标识 audience: "https://your-api-domain.com", // WebAPI的标识 claims: claimsIdentity.Claims, // 携带用户的Claims信息 expires: DateTime.Now.AddHours(1), // Token过期时间 signingCredentials: signingCredentials ); // 把JWT转为字符串,存在Cookie或者localStorage中 var tokenString = new JwtSecurityTokenHandler().WriteToken(jwtToken); Response.Cookies.Append("JwtToken", tokenString, new CookieOptions { HttpOnly = false, Secure = false });
步骤2:修改WebAPI的启动配置,启用JWT认证
同样安装System.IdentityModel.Tokens.Jwt包,然后修改Startup代码:
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions { TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = "https://your-mvc-domain.com", // 和MVC生成时的issuer一致 ValidAudience = "https://your-api-domain.com", // 和MVC生成时的audience一致 IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("你的共享密钥,和MVC里的一样")) } });
步骤3:前端修改Bearer头为JWT Token
// 取刚才生成的JWT Token,而不是Cookie值 var token = getCookie("JwtToken"); $.ajax({ type: "GET", beforeSend: function (request) { request.setRequestHeader("Authorization", "Bearer " + token); }, url: "http://localhost:1231/authapi/api/values/get?id=1", success: function (msg) { alert("Success" + msg) }, error: function (d) { debugger; alert("Error" + d.responseText) } });
方案3:自定义Bearer中间件解析Cookie票据(适配现有Cookie逻辑)
如果不想改动现有的Cookie认证流程,又要坚持使用Bearer头,可以自定义一个Bearer认证提供器,让WebAPI能够解密MVC的Cookie票据:
步骤1:在WebAPI中自定义Bearer认证提供器
public class CookieTicketBearerAuthenticationProvider : OAuthBearerAuthenticationProvider { private readonly string _authenticationType; private readonly MachineKeyProtector _protector; public CookieTicketBearerAuthenticationProvider(string authenticationType) { _authenticationType = authenticationType; // 使用和MVC相同的MachineKey解密,参数要和Owin Cookie内部的保护参数一致 _protector = new MachineKeyProtector( "Microsoft.Owin.Security.Cookies.CookieAuthenticationMiddleware", authenticationType, "v1"); } public override Task RequestToken(OAuthRequestTokenContext context) { // 从Authorization头获取Bearer Token(也就是MVC的Cookie值) var token = context.Request.Headers.Get("Authorization")?.Replace("Bearer ", ""); if (!string.IsNullOrEmpty(token)) { try { // 解密Cookie中的认证票据 var decodedTicket = WebUtility.UrlDecode(token); var ticketBytes = Convert.FromBase64String(decodedTicket); var unprotectedBytes = _protector.Unprotect(ticketBytes); var authenticationTicket = TicketSerializer.Default.Deserialize(unprotectedBytes); // 将解密后的身份信息加入Owin上下文 context.OwinContext.Authentication.SignIn(authenticationTicket.Identity); } catch { // 解密失败时忽略,交给其他认证中间件处理 } } return Task.FromResult<object>(null); } } // 复制Owin内部的MachineKeyProtector实现(因为它是内部类,无法直接引用) public class MachineKeyProtector : IDataProtector { private readonly string[] _purposes; public MachineKeyProtector(params string[] purposes) { _purposes = purposes; } public byte[] Protect(byte[] userData) { return MachineKey.Protect(userData, _purposes); } public byte[] Unprotect(byte[] protectedData) { return MachineKey.Unprotect(protectedData, _purposes); } }
步骤2:修改WebAPI的Bearer认证配置
var OAuthOptions = new OAuthBearerAuthenticationOptions { AuthenticationMode = AuthenticationMode.Active, AuthenticationType = OAuthDefaults.AuthenticationType, // 使用自定义的认证提供器,传入和MVC一致的Auth_type Provider = new CookieTicketBearerAuthenticationProvider("你的Auth_type值") }; app.UseOAuthBearerAuthentication(OAuthOptions);
配置完成后,你之前的前端代码不需要任何修改,直接把.AspNet.ApplicationCookie的值作为Bearer Token发送即可,WebAPI会自动解密并验证身份。
内容的提问来源于stack exchange,提问作者hungryMind

