ASP.NET Core 8 API中基于JWT实现Google身份验证时调用SignInAsync触发InvalidOperationException异常
ASP.NET Core 8 API中基于JWT实现Google身份验证时调用SignInAsync触发InvalidOperationException异常
嗨,我来帮你搞定这个问题!你遇到的这个错误其实是个很典型的误区——咱们得先搞清楚JwtBearerHandler的定位,以及API场景下JWT和Google认证的正确配合逻辑。
错误原因解析
你看到的这个InvalidOperationException核心原因很简单:JwtBearerHandler的唯一职责是验证客户端后续请求发来的JWT令牌,它从设计上就不支持SignInAsync操作。SignInAsync是为Cookie这类有状态认证方案设计的,用来在服务器端创建用户会话,但你的API用的是无状态的JWT,完全不需要这一套会话机制,调用这个方法本身就不符合JWT的使用逻辑。
正确的实现方案
咱们的目标应该是:让用户通过Google登录后,后端验证Google返回的身份凭证,然后自己生成JWT令牌返回给客户端,后续客户端就用这个JWT来访问API接口。下面是具体的实现步骤和代码示例:
1. 配置Google认证与JWT服务(Program.cs)
首先在Program.cs里完成Google认证和JWT验证服务的配置:
var builder = WebApplication.CreateBuilder(args); // 配置Google认证 builder.Services.AddAuthentication() .AddGoogle(options => { // 从配置文件读取Google的ClientId和ClientSecret options.ClientId = builder.Configuration["Authentication:Google:ClientId"]; options.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"]; // 回调路径要和Google开发者控制台里配置的完全一致 options.CallbackPath = "/api/auth/google-callback"; // 可选:自定义映射用户信息到Claims options.ClaimActions.MapJsonKey("urn:google:avatar", "picture", "url"); }) // 配置JWT验证服务,用于后续验证客户端发来的JWT令牌 .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, // 从配置文件读取JWT相关参数 ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) }; }); // 添加JWT令牌生成服务 builder.Services.AddScoped<IJwtTokenGenerator, JwtTokenGenerator>(); builder.Services.AddControllers(); var app = builder.Build(); // 中间件顺序很重要,认证中间件要放在路由和授权中间件前面 app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
2. 实现JWT令牌生成服务
创建一个专门的服务来生成JWT令牌,把逻辑解耦:
public interface IJwtTokenGenerator { string GenerateToken(ClaimsPrincipal userPrincipal); } public class JwtTokenGenerator : IJwtTokenGenerator { private readonly IConfiguration _config; public JwtTokenGenerator(IConfiguration config) { _config = config; } public string GenerateToken(ClaimsPrincipal userPrincipal) { // 可以从Google返回的Principal中获取用户信息,也可以添加自定义Claims var claims = userPrincipal.Claims.ToList(); claims.Add(new Claim(ClaimTypes.Role, "User")); // 示例:添加角色Claim // 生成JWT的签名密钥 var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"])); var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); // 创建JWT令牌 var token = new JwtSecurityToken( issuer: _config["Jwt:Issuer"], audience: _config["Jwt:Audience"], claims: claims, expires: DateTime.UtcNow.AddHours(1), // 设置令牌有效期 signingCredentials: signingCredentials); // 把令牌对象转为字符串 return new JwtSecurityTokenHandler().WriteToken(token); } }
3. 编写Google认证的API端点
创建一个Auth控制器来处理Google登录的跳转和回调逻辑:
[ApiController] [Route("api/auth")] public class AuthController : ControllerBase { private readonly IJwtTokenGenerator _tokenGenerator; public AuthController(IJwtTokenGenerator tokenGenerator) { _tokenGenerator = tokenGenerator; } // 触发Google登录页面的端点 [HttpGet("google")] public IActionResult RedirectToGoogleLogin() { // 配置重定向到回调端点 var authProperties = new AuthenticationProperties { RedirectUri = Url.Action(nameof(HandleGoogleCallback)) }; // 发起Google认证挑战 return Challenge(authProperties, GoogleDefaults.AuthenticationScheme); } // 处理Google认证后的回调 [HttpGet("google-callback")] public async Task<IActionResult> HandleGoogleCallback() { // 获取Google认证的结果 var authResult = await HttpContext.AuthenticateAsync(GoogleDefaults.AuthenticationScheme); if (!authResult.Succeeded) { return BadRequest(new { Message = "Google认证失败,请重试" }); } // 生成JWT令牌 var jwtToken = _tokenGenerator.GenerateToken(authResult.Principal); // 返回令牌给客户端,API场景下直接返回JSON即可 return Ok(new { AccessToken = jwtToken, TokenType = "Bearer", ExpiresIn = 3600 // 单位:秒,和令牌有效期对应 }); } }
4. 配置文件(appsettings.json)示例
确保你的配置文件里有正确的参数:
{ "Authentication": { "Google": { "ClientId": "你的Google开发者控制台ClientId", "ClientSecret": "你的Google开发者控制台ClientSecret" } }, "Jwt": { "Key": "至少16位的强密钥(比如:YourSuperSecretKey123!)", "Issuer": "https://your-api-domain.com", "Audience": "https://your-api-domain.com" } }
几个关键注意点
- 一定要确保Google开发者控制台里的授权重定向URI和你配置的
CallbackPath完全一致,生产环境必须使用HTTPS - API是无状态的,全程不要调用
SignInAsync,客户端拿到JWT后,后续请求要在Authorization请求头里带上Bearer {你的JWT令牌} - JWT的密钥一定要足够强,生产环境不要硬编码,用环境变量或者安全的配置管理工具
备注:内容来源于stack exchange,提问作者ELMAHDI BENHADDOU
相关产品推荐
相关产品推荐

