简体   繁体   中英

ASP.NET Core Web API custom AuthorizeAttribute issue

I am working on ASP.NET Core Web API. I am trying to create a custom Authorize attribute but I am stuck. I could not understand what I am missing. I have the following code for the Authorize attribute and filter:

public class AuthorizeAttribute : TypeFilterAttribute
{
    public AuthorizeAttribute(params string[] claim) : base(typeof(AuthorizeFilter))
    {
        Arguments = new object[] { claim };
    }
}

public class AuthorizeFilter : IAuthorizationFilter
{
    readonly string[] _claim;

    public AuthorizeFilter(params string[] claim)
    {
        _claim = claim;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var IsAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
        var claimsIndentity = context.HttpContext.User.Identity as ClaimsIdentity;

        if (IsAuthenticated)
        {
            bool flagClaim = false;
            foreach (var item in _claim)
            {
                if (context.HttpContext.User.HasClaim("Role", item))
                    flagClaim = true;
            }

            if (!flagClaim)
            {
                //if (context.HttpContext.Request.IsAjaxRequest())
                    context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; //Set HTTP 403 
                //else
                //    context.Result = new RedirectResult("~/Login/Index");
            }
        }
        else
        {
            //if (context.HttpContext.Request.IsAjaxRequest())
            //{
                context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; //Set HTTP 401 -   
            //}
            //else
            //{
            //    context.Result = new RedirectResult("~/Login/Index");
            //}
        }
        return;
    }
}

I have copied this code from somewhere and commented unnecessary lines.

Here is my controller class where I am trying to put this:

[Route("api/[controller]/[action]")]
[ApiController]
[Authorize]
public class JobController : ControllerBase
{
    // GET: api/<JobController>
    [HttpGet]
    [ActionName("GetAll")]
    public List<Job> Get()
    {
        return JobDataLog.GetAllJobQueue();
    }

    // GET api/<JobController>/5
    [HttpGet("{ID}")]
    [ActionName("GetByID")]
    public Job Get(Guid ID)
    {
        return JobDataLog.GetJob(ID);
    }

    // GET api/<JobController>/5
    [HttpGet]
    [ActionName("GetCount")]
    public int GetCount()
    {
        return JobDataLog.GetJobTotal();
    }
}

Also the Configure and ConfigureService methods of Startup.cs

// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddDistributedMemoryCache();
            services.AddSession(options =>
            {
                options.IdleTimeout = TimeSpan.FromMinutes(60);
            });

            var tokenKey = Configuration.GetValue<string>("TokenKey");
            var key = Encoding.ASCII.GetBytes(tokenKey);
            services.AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; })
                .AddJwtBearer(x =>
            {
                x.RequireHttpsMetadata = false;
                x.SaveToken = true;
                x.TokenValidationParameters = new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = new SymmetricSecurityKey(key),
                    ValidateIssuer = false,
                    ValidateAudience = false
                };
            });
            services.AddSingleton<IJWTAuthenticationManager>(new JWTAuthenticationManager(tokenKey));
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseCookiePolicy();
            app.UseSession();
            app.Use(async (context, next) =>
            {
                var JWToken = context.Session.GetString("JWToken");
                if (!string.IsNullOrEmpty(JWToken))
                {
                    context.Request.Headers.Add("Authorization", "Bearer " + JWToken);
                }
                await next();
            });

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }

The problem is that even this controller has the Authorize attribute, all the actions are being called even the Authorize filter invalidates the authorization.

Also when I placed the following code in the OnAuthorization method:

context.Result = new StatusCodeResult(StatusCodes.Status401Unauthorized);

It blocked the access of all actions, including those which have an AllowAnnoynmous attribute.

Please help me, I have been stuck on this for last 3 hours.

If you really want to use a custom AuthorizeAttribute , here you go, this works. :)

You'll have a few squiggly lines, but VS will be able to automatically add the using statements.

The original code had multiple problems:

  1. Setting Reponse.StatusCode doesn't actually lead to a response being returned.
  2. HttpContext.User wouldn't be populated in the first place, because ASP.NET Core only attempts to authenticate the user and populate the user's claims/identity if an endpoint is secured with the built-in AuthorizeAttribute . The following code solves this by deriving from AuthorizeAttribute .
  3. In this case the additional filter factory class wasn't needed, since you're not injecting dependencies. Though, if you had to inject, you'd be out of luck I think, because you couldn't derive both from TypeFilterAttribute and AuthorizeAttribute , and the claims list would be always empty.

Working code

public class MyAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    readonly string[] _requiredClaims;

    public MyAuthorizeAttribute(params string[] claims)
    {
        _requiredClaims = claims;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var isAuthenticated = context.HttpContext.User.Identity.IsAuthenticated;
        if (!isAuthenticated)
        {
            context.Result = new UnauthorizedResult();
            return;
        }

        var hasAllRequredClaims = _requiredClaims.All(claim => context.HttpContext.User.HasClaim(x => x.Type == claim));
        if (!hasAllRequredClaims)
        {
            context.Result = new ForbidResult();
            return;
        }
    }
}

You should probably use policies instead

The reason why this works in such a crappy way is that the ASP.NET Core team doesn't want you to write custom Authorize Attributes . See this answer on the subject . The 'proper' way is to create policies, and assign your claim requirements to those policies. But I also think it's silly that authorization is so inflexible and lacking support for basic scenarios.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM