Caching Claims in .net core 2.0

2019-02-14 13:05发布

问题:

Looked up everywhere but looks like I am stuck right now. I am using Windows Active Directory in my application for authentication. For authorization, I am using claims. After searching through the limited .net core documentation, this is how my code looks like.

Startup.cs

public void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IPrincipal>(
            provider => provider.GetService<IHttpContextAccessor>().HttpContext.User);
        services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
        services.AddAuthentication(IISDefaults.AuthenticationScheme);

    }

ClaimsTransformer.cs

class ClaimsTransformer : IClaimsTransformation
{
   public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
   {

// call to database to get more claims based on user id ClaimsIdentity.Name
     ((ClaimsIdentity)principal.Identity).AddClaim(new Claim("now",DateTime.Now.ToString()));
     return Task.FromResult(principal);
   }
}

But the problem is, this code is called with every request and claims are loaded from the db every time which is absolutely wrong. Is there any way I can cache it? I was able to create a cookie of claims and use that cookie for any further calls in .net 4.0. I can't seem to find a way in the core. Any documentation I check, is incomplete or it does not cover my scenario. I am able to claims further in my application just how the documentation says here: https://docs.microsoft.com/en-us/aspnet/core/security/authorization/claims

But there is no mention about caching the claims.

Anyone in the same boat? Or knows the way out of it?

回答1:

You can inject the IMemoryCache service in your ClaimsTransformer constructor.

using Microsoft.Extensions.Caching.Memory;

public class ClaimsTransformer : IClaimsTransformation
{
    private readonly IMemoryCache _cache;

    public ClaimsTransformer(IMemoryCache cache)
    {
        _cache = cache;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        var cacheKey = principal.FindFirstValue(ClaimTypes.NameIdentifier);

        if (_cache.TryGetValue(cacheKey, out List<Claim> claims)
        {
            ((ClaimsIdentity)principal.Identity).AddClaims(claims);
        }
        else
        {
            claims = new List<Claim>();          

            // call to database to get more claims based on user id ClaimsIdentity.Name

            _cache.Set(cacheKey, claims);
        }

        return principal;
    }
}


回答2:

I am not doing the exact same thing, but I am using cookie Authentication/Authorization. Most of what I learned comes from this microsoft doc but as you said the documentation doesn't seem to take you all the way there. Here is what is working for me:

in startup.cs

public void ConfigureServices(IServiceCollection services)
    {
        ...

        services.AddAuthentication("tanushCookie")
        .AddCookie("tanushCookie", options => {
            options.AccessDeniedPath = "/api/Auth/Forbidden";
            options.LoginPath = "/";
            options.Cookie.Expiration = new TimeSpan(7,0,0,0);
        });
    }


public void Configure(IApplicationBuilder app, 
                      IHostingEnvironment env, 
                      ILoggerFactory loggerFactory)
    {
        ...

        app.UseAuthentication();
    }

And then in your controller that handles authentication:

    [HttpPost()]
    [Route("api/[Controller]/[Action]/")]
    public async Task<JsonResult> Login([FromBody]Dictionary<string, string> loginData)
    {
        try
        {
            var loggedIn = true;
            if (loggedIn)
            {
                var claims = new List<Claim> {
                    new Claim(ClaimTypes.Name, "tanush")
                };

                var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme);
                identity.AddClaims(claims);
                ClaimsPrincipal principal = new ClaimsPrincipal(identity);

                await HttpContext.SignInAsync(
                    "tanushCookie",
                    principal,
                    new AuthenticationProperties
                    {
                        IsPersistent = true,
                        ExpiresUtc = DateTime.UtcNow.AddDays(7)
                    });
            }
            return new JsonResult(logRtn);
        }
        catch (Exception ex)
        {
            return new JsonResult(ex.Message);
        }
    }

I am unsure if you can use cookies with windows authentication. However if you can authenticate and assign loggedIn the result of your authentication request, you should be able to store some sort of claim(s) in the cookie. You can then recall that claim in a controller that might be doing authorization/recalling values using the following:

    [HttpGet("[Action]", Name = "GetSomething")]
    [Route("[Action]")]
    public JsonResult something()
    {
        try
        {
            var loggedInUser = HttpContext.User;
            var claym = loggedInUser.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Name);
            if (claym != null)
            {
                return new JsonResult(claym.Value);
                // returns "tanush"
            }
            else
            {
                return new JsonResult("");
            }
        }
        catch (Exception ex)
        {
            return new JsonResult(ex.Message);
        }
    }