ASP.NET MVC项目生成Bearer Token用于ASP.NET Web API认证
嘿,我之前也碰到过几乎一模一样的场景——两个基于OWIN+Identity的项目,一个用Cookie做前端认证,一个用Token做API接口认证,要打通两者的身份体系。核心思路其实就是复用Identity的用户数据源,要么直接在MVC里生成符合API要求的Token,要么让MVC调用API的Token端点去获取。下面给你两种可行的方案,一步步来:
方案1:直接在MVC项目中生成JWT Token(推荐,更高效)
这种方式不需要额外调用API的Token端点,直接在MVC里用和API完全一致的密钥、签名算法生成Token,API就能无缝验证通过。
第一步:共享Identity核心组件(可选但推荐)
把两个项目共用的Identity代码抽成一个类库,避免重复配置,同时保证用户数据完全一致:
- 创建一个类库项目,添加
Microsoft.AspNet.Identity.EntityFrameworkNuGet包 - 定义你的
ApplicationUser和ApplicationDbContext:
public class ApplicationUser : IdentityUser { // 这里可以添加自定义用户属性,比如DisplayName、AvatarUrl之类的 } public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { public ApplicationDbContext() : base("YourSharedConnectionString") { } public static ApplicationDbContext Create() => new ApplicationDbContext(); }
- 让MVC和Web API项目都引用这个类库,替换各自原来的Identity相关代码。
第二步:在MVC的Startup中配置JWT参数
必须保证和Web API的JWT配置完全匹配(密钥、Issuer、Audience),否则生成的Token会被API拒绝:
首先添加NuGet包:Microsoft.Owin.Security.Jwt、System.IdentityModel.Tokens.Jwt
然后在Startup.cs的Configuration方法里,保留原来的Cookie认证,再加上JWT相关配置:
public void Configuration(IAppBuilder app) { // 原来的Cookie认证配置(保留不动) app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>( validateInterval: TimeSpan.FromMinutes(30), regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)) } }); // JWT配置(和Web API的配置完全一致!) var jwtSettings = new { Issuer = "https://your-app-domain.com", // 示例域名,和API的Issuer一致 Audience = "https://your-api-domain.com", // 示例API域名,和API的Audience一致 SecretKey = "YourSuperSecureKey_AtLeast16CharactersLong" // 必须和API用的密钥完全相同 }; var symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey)); // 可选:如果MVC自身也要调用API,配置JWT验证(不影响Token生成逻辑) app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions { AuthenticationMode = AuthenticationMode.Active, TokenValidationParameters = new TokenValidationParameters { ValidIssuer = jwtSettings.Issuer, ValidAudience = jwtSettings.Audience, IssuerSigningKey = symmetricKey } }); }
第三步:在MVC登录逻辑中生成Token
在AccountController的Login方法里,用户登录成功后,生成Token并存储到安全的Cookie中(方便前端调用API时取用):
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginViewModel model, string returnUrl) { if (!ModelState.IsValid) return View(model); var signInResult = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false); switch (signInResult) { case SignInStatus.Success: // 获取当前登录用户 var user = await UserManager.FindByEmailAsync(model.Email); // 生成符合API要求的JWT Token var apiToken = GenerateJwtToken(user); // 把Token存到HttpOnly+Secure的Cookie里(避免XSS攻击,生产环境必须开Secure) Response.Cookies.Append("ApiBearerToken", apiToken, new CookieOptions { HttpOnly = true, Secure = Request.IsSecureConnection, Expires = DateTime.UtcNow.AddHours(1) }); return RedirectToLocal(returnUrl); // 处理登录失败的情况 case SignInStatus.LockedOut: return View("Lockout"); default: ModelState.AddModelError("", "Invalid login attempt."); return View(model); } } // 核心方法:生成JWT Token private string GenerateJwtToken(ApplicationUser user) { var jwtSettings = new { Issuer = "https://your-app-domain.com", // 和Startup里的配置一致 Audience = "https://your-api-domain.com", // 和Startup里的配置一致 SecretKey = "YourSuperSecureKey_AtLeast16CharactersLong" // 和Startup里的配置一致 }; var symmetricKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey)); var signingCredentials = new SigningCredentials(symmetricKey, SecurityAlgorithms.HmacSha256); // 组装用户声明(API需要的用户身份信息) var claims = new List<Claim> { new Claim(JwtRegisteredClaimNames.Sub, user.Email), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), new Claim(ClaimTypes.NameIdentifier, user.Id) }; // 添加用户角色(如果API需要权限验证的话) var userRoles = await UserManager.GetRolesAsync(user.Id); claims.AddRange(userRoles.Select(role => new Claim(ClaimTypes.Role, role))); // 创建并序列化Token var token = new JwtSecurityToken( issuer: jwtSettings.Issuer, audience: jwtSettings.Audience, claims: claims, expires: DateTime.UtcNow.AddHours(1), // Token有效期 signingCredentials: signingCredentials ); return new JwtSecurityTokenHandler().WriteToken(token); }
方案2:调用Web API的/Token端点获取Token
如果不想在MVC里添加Token生成逻辑,也可以让MVC在用户登录后,模拟POST请求到API的/Token端点,直接获取Bearer Token:
实现代码
在AccountController里添加一个获取Token的辅助方法:
private async Task<string> FetchApiToken(string username, string password) { using var httpClient = new HttpClient(); var tokenRequest = new HttpRequestMessage(HttpMethod.Post, "https://your-api-domain.com/Token"); // 构造Token端点要求的表单数据 var formData = new List<KeyValuePair<string, string>> { new("grant_type", "password"), new("username", username), new("password", password) }; tokenRequest.Content = new FormUrlEncodedContent(formData); var response = await httpClient.SendAsync(tokenRequest); if (!response.IsSuccessStatusCode) return null; // 解析返回的Token数据 var tokenResponse = await response.Content.ReadFromJsonAsync<dynamic>(); return tokenResponse.access_token; }
然后在Login成功后调用这个方法:
case SignInStatus.Success: var apiToken = await FetchApiToken(model.Email, model.Password); if (!string.IsNullOrEmpty(apiToken)) { // 存储Token到安全Cookie中 Response.Cookies.Append("ApiBearerToken", apiToken, new CookieOptions { HttpOnly = true, Secure = true }); } return RedirectToLocal(returnUrl);
关键注意事项
- 配置一致性:两种方案都要求MVC和API的Identity配置完全一致——尤其是密钥、Issuer、Audience,否则API会拒绝验证Token。
- 安全性:Token必须通过HTTPS传输,存储时优先用
HttpOnly+Secure的Cookie,避免XSS攻击。 - 版本兼容:确保两个项目的OWIN、Identity相关NuGet包版本完全一致,避免依赖冲突。
内容的提问来源于stack exchange,提问作者TripWire

