How can I populate a UserAuth from values in Redis?

可紊 提交于 2019-12-11 08:08:59

问题


This is my custom user authentication setup in my global.asax file, but I am currently providing the users manually in the Configure method; Is it possible to take values from a Redis server?

For example if user exists and the password is okay, can fill with these details automatically?

Plugins.Add(new AuthFeature(()=> 
    new AuthUserSession(), 
    new IAuthProvider[]{ new BasicAuthProvider() }
));

container.Register<ICacheClient>(new MemoryCacheClient());
var userRepo = new InMemoryAuthRepository();
container.Register<IUserAuthRepository>(userRepo);

string hash, salt;
new SaltedHash().GetHashAndSaltString("password", out hash, out salt);

userRepo.CreateUserAuth(new UserAuth
{
    Id = 1,
    DisplayName = "Haluk",
    Email = "hal",
    UserName = "haluk",
    FirstName = "haluk",
    LastName = "yılmaz",
    PasswordHash = hash,
    Salt = salt
}, "password");

回答1:


Yes you can authenticate against a Redis data source. You can either use the built in RedisAuthRepository in place of the InMemoryAuthRepository, or if you have an existing Redis data set that you want to use instead of the built-in IAuthRepository pattern, I have included a solution for that, whereby you extend the BasicAuthProvider. The first method is the most straightforward:

Use the RedisAuthRepository:

  1. So you need to establish a connection to Redis.
  2. Then register your authentication providers.
  3. Register the RedisAuthRepository, which the authentication providers will check credentials against, and is compatible with the RegistrationFeature
private IRedisClientsManager redisClientsManager;

public override void Configure(Funq.Container container)
{
    // Configure ServiceStack to connect to Redis
    // Replace with your connection details
    redisClientsManager = new PooledRedisClientManager("127.0.0.1:6379");
    container.Register<IRedisClientsManager>(c => redisClientsManager);
    container.Register<ICacheClient>(c => c.Resolve<IRedisClientsManager>().GetCacheClient()).ReusedWithin(Funq.ReuseScope.None);

    // Setup the authorisation feature
    Plugins.Add(new AuthFeature(()=> 
        new AuthUserSession(),
        new IAuthProvider[]{ new BasicAuthProvider() }
    ));

    // Use a RedisAuthRepository
    var userRepo = new RedisAuthRepository(redisClientsManager);
    container.Register<IUserAuthRepository>(userRepo);

    // You can then register users as required using the RegistrationFeature
}

Alternatively (if you have an existing user authentication dataset in Redis)

You can do this by creating a custom authentication provider that extends the existing BasicAuthProvider.

For this code you should also make sure that your familiar with the ServiceStack.Redis client.

Extend the BasicAuthProvider:

This MyRedisBasicAuthProvider extends the existing BasicAuthProvider, and instead of performing the credentials lookup from an IUserAuthRepository as given in your example code, it makes a Redis connection and matches the username to entry in Redis.

The code is fully commented but if there is anything you wish further explained, let me know.

public class MyRedisBasicAuthProvider : BasicAuthProvider
{
    // The key at which we will store the user profile. i.e user:john.smith or user:homer.simpson
    // Replace this key with your format as required
    public const string UserKeyFormat = "user:{0}";

    MyUser CurrentUser;

    // Gets an instance of a redis client
    static IRedisClient GetRedisClient()
    {
        // Get the RedisClientsManager from the Container
        var redisClientManager = HostContext.TryResolve<IRedisClientsManager>();
        if(redisClientManager == null)
            throw new Exception("Redis is not configured");

        // Return a client
        return redisClientManager.GetClient();
    }

    // This method is used to verify the credentials provided
    public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
    {
        // Get a Redis client connection
        using(var redisClient = GetRedisClient())
        {
            // Get a typed Redis Client
            var userClient = redisClient.As<MyUser>();

            // Try to find a matching user in Redis
            CurrentUser = userClient.GetValue(string.Format(UserKeyFormat, userName));

            // Check the user exists & their password is correct (You should use a hashed password here)
            return CurrentUser != null && password == CurrentUser.Password;
        }
    }

    // This method is used to populate the session details from the user profile and other source data as required
    public override IHttpResult OnAuthenticated(IServiceBase authService, IAuthSession session, IAuthTokens tokens, Dictionary<string, string> authInfo)
    {
        // Populate the session with the details of the current user
        session.PopulateWith<IAuthSession, MyUser>(CurrentUser);

        // Save the session
        authService.SaveSession(session);

        return null;
    }

    public static void AddUserToRedis(MyUser user)
    {
        using(var redisClient = GetRedisClient())
        {
            // Get a typed Redis Client
            var userClient = redisClient.As<MyUser>();

            // Add the user to Redis
            userClient.SetEntry(string.Format(UserKeyFormat, user.Username), user);
        }
    }
}

In the code above I have used a class MyUser to represent the user profile that I have stored in Redis, you can of course customise this class to match your user profile requirements. So this is the basic user profile class:

public class MyUser
{
    public string Username { get; set; }
    public string Password { get; set; } // Replace with a hashed password
    public string Email { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Setting up ServiceStack with Redis & your custom Authentication Provider:

You will need to configure ServiceStack to use Redis and tell it to use your custom authentication provider. You do this by adding the following to your Configure method in your AppHost:

public override void Configure(Funq.Container container)
{
    // Configure ServiceStack to connect to Redis
    // Replace with your connection details
    container.Register<IRedisClientsManager>(c => new PooledRedisClientManager("127.0.0.1:6379"));
    container.Register<ICacheClient>(c => c.Resolve<IRedisClientsManager>().GetCacheClient()).ReusedWithin(Funq.ReuseScope.None);

    // Add your custom credentials provider
    Plugins.Add(new AuthFeature(() => new AuthUserSession(),
        new IAuthProvider[] {
            new MyRedisBasicAuthProvider()
        }
    ));

    // Add some test users. (If you have an existing Redis user source, you won't need to add test users.)
    MyRedisBasicAuthProvider.AddUserToRedis(new MyUser {
        Username = "john.smith",
        Password = "test",
        Email = "john.smith@email.com",
        FirstName = "John",
        LastName = "Smith",
    });

    MyRedisBasicAuthProvider.AddUserToRedis(new MyUser {
        Username = "homer.simpson",

        Password = "donuts",
        Email = "homer.simpsons@springfield.com",
        FirstName = "Homer",
        LastName = "Simpson",
    });

    // Your other configuration settings ...
}

Notes:

In the example I haven't used a hash password, to keep the example straightforward, but this is trivial to do. Add another field public string Salt { get; set; } to the MyUser then instead of storing the plain password on MyUser store it as a hash of the password and salt i.e. hashedPassword = HashAlgorithm(password + salt). You already have code for it:

string hash, salt;
new SaltedHash().GetHashAndSaltString("password", out hash, out salt);

So this solution will now use a Redis data source to authenticate users when a service is secured using the [Authenticate] attribute. As with the standard basic provider, the credentials are authenticated at the standard /auth/basic route.

Using the Credentials provider instead of Basic:
If you want to use a credentials provider for form posts, instead of Basic authentication you can simple replace the word Basic with Credentials in the code above.

I hope this helps.



来源:https://stackoverflow.com/questions/24798360/how-can-i-populate-a-userauth-from-values-in-redis

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