IdentityServer / IdentityServer3

OpenID Connect Provider and OAuth 2.0 Authorization Server Framework for ASP.NET 4.x/Katana
https://identityserver.github.io/Documentation/
Apache License 2.0
2.01k stars 764 forks source link

How to flush cache or mark item dirty #3309

Closed gilm0079 closed 7 years ago

gilm0079 commented 8 years ago

We have been running into some issues with our identity server implementation where if a user account is created and used then the claim values are changed in Membership Reboot the identity server keeps using the old claim values when issuing identity tokens. I have performed a Logout and Login to destroy the identity token and that doesn't help. What seems to work is restarting the IdentityServer App Pool in IIS. We are also using Identity Server in a web farm (2 servers) and using a Redis Cache for Scopes, Clients and Users. When we change claim information we do so via a REST call to a userAccountService on server A. We've had to restart the servers on both A and B sometimes so it isn't a problem of something not getting propagated to the B server.

Should we be calling a method in the IdentityServer after modifying claims to flush the cache or mark the user's claims as dirty?

brockallen commented 8 years ago

We have been running into some issues with our identity server implementation where if a user account is created and used then the claim values are changed in Membership Reboot the identity server keeps using the old claim values when issuing identity tokens.

This would be up to how your user service is implemented.

gilm0079 commented 8 years ago

Correct. Sorry, I probably worded that poorly. I'm looking to update our user service to flush the cache. We are using the Identity Server's cache implementation of using the IdentityServerServiceFactory in the startup.cs and executing the ConfigureUserServiceCache method on it. It looks like the ICacheManager used for the UserServiceCache that is registered exposes a bunch of methods to manipulating the cache.

I'm probably answering my own question, but I'm assuming using this to call a remove on the keys would be the appropriate action?

Edit: Sorry. One more thing I realized. It looks like our user service does not make any calls to the cache manager. Our custom user service is basically a wrapper for BrockAllen.MembershipReboot.UserAccountService. We do some additional tasks before calling into the MembershipReboot user service. For instance our AddClaim calls the MembershipReboot AddClaim. I think we were assuming that user service handles the cache layer. Are we incorrect and we need to handle it ourselves?

brockallen commented 8 years ago

I'm confused. We don't have a ICacheManager interface anywhere in IdentityServer3. The UserServiceCache uses a ICache<IEnumerable<Claim>> which is implemented by the DefaultCache<T> which is just simple in-memory, timeout-based caching based on the .NET class MemoryCache.

gilm0079 commented 8 years ago

Sorry, I was just looking at namespaces. Looks like ICacheManager comes from IdentityServer3.Contrib.Redis.CacheClient.

Here is a snippet of how the user service cache is registered. Factory being the IdentityServerServiceFactory instance and UserServiceCache (IdentityServer3.Contrib.Cache.Redis.UserServiceCache) that inherits from ICache<IEnumerable<Claim>>. cacheConnection being the implementation of the Redis Cache component.

var userServiceCache = new UserServiceCache(cacheConnection);
factory.ConfigureUserServiceCache(new Registration<ICache<IEnumerable<Claim>>>(userServiceCache))

Here is the UserServiceCache. Maybe more cache methods need to be exposed for the IdentityServerServiceFactory to use besides the Get and Set Async methods?

using System;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using StackExchange.Redis;
using StackExchange.Redis.Extensions.Core;
using StackExchange.Redis.Extensions.Newtonsoft;
using IdentityServer3.Core.Services;
using IdentityServer3.Contrib.Cache.Redis.CacheClient;

namespace IdentityServer3.Contrib.Cache.Redis
{
    public class UserServiceCache : ICache<IEnumerable<Claim>>
    {
        private readonly ICacheManager cacheClient;

        public UserServiceCache(ConnectionMultiplexer connection)
            : this(new RedisCacheManager(connection))
        {
        }

        public UserServiceCache(ICacheManager cacheClient)
        {
            this.cacheClient = cacheClient;
        }

        public Task<IEnumerable<Claim>> GetAsync(string key)
        {
            return cacheClient.GetAsync<IEnumerable<Claim>>(key);
        }

        public Task SetAsync(string key, IEnumerable<Claim> item)
        {
            return cacheClient.AddAsync(key, item, TimeSpan.FromMinutes(30));
        }
    }
}

I'm assuming the cache registration provides an interface for the IdentityServer project to perform user caching against if registered to the factory?

Maybe our assumption is wrong. I think we thought that if a cache was registered with the factory that a UserService registered with the factory would also make use of user cache. In our case the UserService we register inherits from MembershipRebootUserService. So if we call the AddClaim method on the BrockAllen.MembershipReboot.UserAccountService should that be handling the cache updates or do we also need to access the factory's cache implementation to make the appropriate updates against the cache?

Let me know if you have any questions. I hope I'm not making this confusing.

brockallen commented 8 years ago

Our caching strategy is achieved via a decorator pattern. So we have 2 different IUserService implementations -- one that's the real/inner one, and then the one that's the outer one that is actually used by IdSvr.

Since your cache needs to be smarter, then perhaps you can't really tie into our ICache style. Instead you can/shoul;d just do caching on your own in the GetProfile API in the user service. That way the purge logic can be better integrated with your DB (when the user is updated) and you have total control over it.

Does this help? I still feel like I'm missing something...