我正在开发一个使用 ASP.NET MVC 核心作为后端 API 的 Angular SPA,我在让我的 JWT 身份验证正常工作时遇到了很多麻烦。
我使用 Openiddict 作为我的 JWT 中间件来发行令牌。我可以成功地向我的控制器发送令牌请求,让用户登录,然后将令牌发送回客户端。
但是,当我尝试访问受保护的 API 路由时,我收到了 401 未经授权的消息。我可以看到令牌是在请求的标头中发送的,所以它一定不能在服务器端正确读取,所以我认为这只是一个配置问题。我为此查看的每一个资源都让人觉得没有其他明智的配置可做。
以下是代码的相关部分:
启动配置服务
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthorization();
services.AddEntityFramework()
.AddEntityFrameworkSqlServer()
.AddDbContext<DbContext>();
services.AddIdentity<User, IdentityRole>(config =>
{
config.User.RequireUniqueEmail = true;
config.Password.RequiredLength = 8;
})
.AddEntityFrameworkStores<DbContext>()
.AddDefaultTokenProviders();
services.AddOpenIddict<DbContext>()
.AddMvcBinders()
.EnableTokenEndpoint("/auth/token")
.UseJsonWebTokens()
.AllowPasswordFlow()
.AddEphemeralSigningKey()
//.AddSigningCertificate()
.DisableHttpsRequirement();
services.AddLogging();
}
启动配置应用程序
public void Configure(IApplicationBuilder app, RequestJockeyDataSeeder seeder, ILoggerFactory factory)
{
app.UseDefaultFiles(new DefaultFilesOptions()
{
DefaultFileNames = new[] { "index.html" }
});
app.UseStaticFiles();
app.UseOpenIddict();
app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Audience = "http://localhost:5000",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
});
app.UseMvc();
}
控制器- 我从网上的一个例子中得到这个
[HttpPost("~/auth/token"), Produces("application/json")]
public async Task<IActionResult> Token(OpenIdConnectRequest request)
{
if (!request.IsPasswordGrantType())
{
// Return bad request if the request is not for password grant type
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
ErrorDescription = "The specified grant type is not supported."
});
}
var user = await _userManager.FindByNameAsync(request.Username);
if (user == null)
{
// Return bad request if the user doesn't exist
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "Invalid username or password"
});
}
// Check that the user can sign in and is not locked out.
// If two-factor authentication is supported, it would also be appropriate to check that 2FA is enabled for the user
if (!await _signInManager.CanSignInAsync(user) || (_userManager.SupportsUserLockout && await _userManager.IsLockedOutAsync(user)))
{
// Return bad request is the user can't sign in
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "The specified user cannot sign in."
});
}
if (!await _userManager.CheckPasswordAsync(user, request.Password))
{
// Return bad request if the password is invalid
return BadRequest(new OpenIdConnectResponse
{
Error = OpenIdConnectConstants.Errors.InvalidGrant,
ErrorDescription = "Invalid username or password"
});
}
// The user is now validated, so reset lockout counts, if necessary
if (_userManager.SupportsUserLockout)
{
await _userManager.ResetAccessFailedCountAsync(user);
}
// Create the principal
var principal = await _signInManager.CreateUserPrincipalAsync(user);
// Claims will not be associated with specific destinations by default, so we must indicate whether they should
// be included or not in access and identity tokens.
foreach (var claim in principal.Claims)
{
// For this sample, just include all claims in all token types.
// In reality, claims' destinations would probably differ by token type and depending on the scopes requested.
claim.SetDestinations(OpenIdConnectConstants.Destinations.AccessToken, OpenIdConnectConstants.Destinations.IdentityToken);
}
// Create a new authentication ticket for the user's principal
var ticket = new AuthenticationTicket(
principal,
new AuthenticationProperties(),
OpenIdConnectServerDefaults.AuthenticationScheme);
// Include resources and scopes, as appropriate
var scope = new[]
{
OpenIdConnectConstants.Scopes.OpenId,
OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess,
OpenIddictConstants.Scopes.Roles
}.Intersect(request.GetScopes());
ticket.SetResources("http://localhost:5000/");
ticket.SetScopes(scope);
ticket.Properties.ExpiresUtc = DateTimeOffset.Now.AddMinutes(15);
// Sign in the user
return SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme);
}
我将省略 angular http 拦截器,因为路由通过应用程序和 Postman 都失败了。就配置 MVC 以正确处理这些令牌而言,我有什么遗漏吗?