You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET MVC项目生成Bearer Token用于ASP.NET Web API认证

从ASP.NET MVC(Cookie认证)生成Web API可用的Bearer Token

嘿,我之前也碰到过几乎一模一样的场景——两个基于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.EntityFramework NuGet包
  • 定义你的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);

关键注意事项

  1. 配置一致性:两种方案都要求MVC和API的Identity配置完全一致——尤其是密钥、Issuer、Audience,否则API会拒绝验证Token。
  2. 安全性:Token必须通过HTTPS传输,存储时优先用HttpOnly+Secure的Cookie,避免XSS攻击。
  3. 版本兼容:确保两个项目的OWIN、Identity相关NuGet包版本完全一致,避免依赖冲突。

内容的提问来源于stack exchange,提问作者TripWire

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 07:53:54