run additional logic besides [Authorize] annotation

好久不见. 提交于 2021-02-10 00:07:43

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!