问题
I'm using the Microsoft.AspNetCore.Authentication.JwtBearer and System.IdentityModel.Tokens.Jwt for my .NET Core project.
Whenever I generate a new token I store that to the database. When a user signs out, I remove it from the database to invalidate it (I also remove the expired ones from the database with a job). When a user tries to access a route protected by the [Authorize]
annotation I want to check if that token exists in the database. If not, I send a 401.
In my Startup in the Configure
method I'm calling app.UseAuthentication()
and in the ConfigureServices
method I setup the validation for the [Authorize]
annotation (I tried to comment it to show what I want to achieve)
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(jwtBearerOptions =>
{
byte[] symmetricKey = Convert.FromBase64String("secretFromConfig");
SymmetricSecurityKey symmetricSecurityKey = new SymmetricSecurityKey(symmetricKey);
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ValidateIssuer = false,
ValidateAudience = false,
IssuerSigningKey = symmetricSecurityKey,
};
jwtBearerOptions.Events = new JwtBearerEvents()
{
OnTokenValidated = tokenValidatedContext =>
{
// inject my database repository
ITokensRepository tokensRepository = tokenValidatedContext.HttpContext.RequestServices.GetRequiredService<ITokensRepository>();
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
// convert the token to a string
string token = tokenHandler.WriteToken(tokenValidatedContext.SecurityToken);
// read the username from the token payload
Claim usernameClaim = tokenValidatedContext.Principal.Claims.FirstOrDefault(claim => claim.Type == "username");
// assign if exists
string username = usernameClaim?.Value;
// fetch token from database
object tokenInfo = await tokensRepository.GetUserTokenAsync(token, username);
if (tokenInfo == null)
{
// return a 401 if token doesn't exist in database
tokenValidatedContext.Fail("Invalid token.");
// return new UnauthorizedResult();
}
else
{
tokenValidatedContext.Success();
// controller methods can fetch it from here later on
tokenValidatedContext.HttpContext.Items["username"] = username;
}
return Task.CompletedTask;
}
};
});
When I try to make the OnTokenValidated
callback async for the repository call I get the error message
Return type is 'void'
How can I solve it? The callback has to be async because I'm accessing the database. Any help will highly appreciated!
回答1:
I believe one of the better ways to do this is through adding a policy to your Authorization. (Similar to how it is handled if you want to create a claims policy)
If you want the policy only for certain authorize tage
Startup.cs
services.AddAuthorization(authorizationOptions =>
{
authorizationOptions.AddPolicy(
"MustHaveTokenInDb",
policyBuilder =>
{
//add any other policy requirements here too including ones by default
//eg policyBuilder.RequireAuthenticatedUser();
policyBuilder.AddRequirements(
new MustHaveTokenInDbRequirement());
});
//only if you want to register as the default policy
authorizationOptions.DefaultPolicy = authorizationOptions.GetPolicy("MustHaveTokenInDb");
});
Then in the authorize
Tag you use
[Authorize("MustHaveTokenInDB")]
You can set a default policy either which allows you to use the Authorize tag as normal
authorizationOptions.DefaultPolicy = authorizationOptions.GetPolicy("MustHaveTokenInDb");
Your requirement class
public class MustHaveTokenInDbRequirement : IAuthorizationRequirement
{
public MustHaveTokenInDbRequirement ()
{
}
}
Handler class
public class MustHaveTokenInDbHandler : AuthorizationHandler<MustHaveTokenInDbRequirement >
{
public MustHaveTokenInDbHandler ()
{
//your dependency injections
}
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MustHaveTokenInDbRequirement requirement)
{
//your logic
if (noTokenInDb)
{
context.Fail();
return Task.CompletedTask;
}
// has token in db
context.Succeed(requirement);
return Task.CompletedTask;
}
}
In your configure services you need to register it:
services.AddScoped<IAuthorizationHandler, MustHaveTokenInDbHandler>();
来源:https://stackoverflow.com/questions/62947768/run-additional-logic-besides-authorize-annotation