ServiceStack使用多个存储库进行身份验证

时间:2014-01-23 01:08:44

标签: c# .net authentication servicestack

我们可以为servicestack设置多个AuthProviders。但他们都应该使用一个用户存储库。有没有办法设置一个基本的auth提供程序和两个不同的存储库用于不同的路由?

1 个答案:

答案 0 :(得分:5)

没有开箱即用的多个存储库:

简短的回答是,你不能简单地使用多个存储库。它不支持这一点的原因是因为CredentialsProvider使用AppHost依赖性容器(IoC)来解析IUserAuthRepository,所以它只需要一个存储库,此外,还需要有关路线中使用哪个存储库的更多信息

您可以编写自己的CredentialsProvider,然后编写一个新的BasicAuthProvider,但这对于简单的基本身份验证来说是很多工作。如果您使用InMemoryAuthRepositoryRedisAuthRepository,您会发现即使您创建了单独的实例,存储库实际上也会合并,因为它们使用相同的缓存键。 :(

// This doesn't work. The repositories will merge.

var repository1 = new InMemoryAuthRepository();
repository1.CreateUserAuth(new UserAuth { Id = 1, UserName = "cburns", FullName = "Charles Montgomery Burns" }, "excellent");
repository1.CreateUserAuth(new UserAuth { Id = 2, UserName = "bartsimpson", FullName = "Bart Simpson" }, "Ay caramba");
repository1.CreateUserAuth(new UserAuth { Id = 3, UserName = "homersimpson", FullName = "Homer J. Simpson" }, "donuts");

var repository2 = new InMemoryAuthRepository();
repository2.CreateUserAuth(new UserAuth { Id = 1, UserName = "thehulk", FullName = "The Hulk" }, "pebbles");
repository2.CreateUserAuth(new UserAuth { Id = 2, UserName = "captainamerican", FullName = "Captain America" }, "redwhiteblue");
repository2.CreateUserAuth(new UserAuth { Id = 3, UserName = "spiderman", FullName = "Spider Man" }, "withgreatpower");

实施您自己的身份验证:

ServiceStack具有很好的可扩展性,您可以轻松地对自己的身份验证进行角色扮演。 Basic Auth是一个非常简单的协议。

我只是创建了这个RequestFilterAttribute,允许您使用任意数量的自定义存储库。

Full Source Code Here ServiceStack v4自托管应用

简单的自定义存储库。 您可以使用更复杂的存储库,并包含数据库查找等。但这很简单,仅用于演示目的:

public class MyUserRepository
{
    public string Name { get; set; }
    public Dictionary<string, string> Users { get; set; }

    public MyUserRepository(string name, Dictionary<string, string> users = null)
    {
        Name = name;
        Users = users ?? new Dictionary<string, string>();
    }
}

RequestFilterAttribute 即。进行身份验证

public class BasicAuthAttribute : RequestFilterAttribute {

    readonly string _realmName;
    readonly string _repositoryName;

    public BasicAuthAttribute(string realmName, string repositoryName = null)
    {
        _realmName = realmName;
        _repositoryName = repositoryName ?? realmName;
    }

    public override void Execute(IRequest req, IResponse res, object requestDto)
    {
        // Get the correct repository to authenticate against
        var repositories = HostContext.TryResolve<MyUserRepository[]>();
        MyUserRepository repository = null;
        if(repositories != null)
            repository = repositories.FirstOrDefault(r => r.Name == _repositoryName);

        // Determine if request has basic authentication
        var authorization = req.GetHeader(HttpHeaders.Authorization);

        if(repository != null && !String.IsNullOrEmpty(authorization) && authorization.StartsWith("basic", StringComparison.OrdinalIgnoreCase))
        {
            // Decode the credentials
            var credentials = Encoding.UTF8.GetString(Convert.FromBase64String(authorization.Substring(6))).Split(':');
            if(credentials.Length == 2)
            {
                // Try and match the credentials to a user
                var password = repository.Users.GetValueOrDefault(credentials[0]);
                if(password != null && password == credentials[1])
                {
                    // Credentials are valid
                    return;
                }
            }
        }

        // User requires to authenticate
        res.StatusCode = (int)HttpStatusCode.Unauthorized;
        res.AddHeader(HttpHeaders.WwwAuthenticate, string.Format("basic realm=\"{0}\"", _realmName));
        res.EndRequest();
    }
}

用法:它的用法很简单。使用属性

装饰您的操作方法或DTO
public static class TestApp
{

    [Route("/TheSimpsons", "GET")]
    public class TheSimpsonsRequest {}

    [Route("/Superheros", "GET")]
    public class SuperherosRequest {}

    public class TestController : Service
    {
        [BasicAuth("The Simpsons", "Simpsons")] // Requires a 'Simpsons' user
        public object Get(TheSimpsonsRequest request)
        {
            return new { Town = "Springfield", Mayor = "Quimby" };
        }

        [BasicAuth("Superheros")] // Requires a user from 'Superheros'
        public object Get(SuperherosRequest request)
        {
            return new { Publishers = new[] { "Marvel", "DC" } };
        }
    }
}

[BasicAuth( string realmName, string repositoryName)]

  • realmName是要在身份验证对话框中显示的名称
  • repositoryName是要查找凭据的存储库的名称。它是可选的,如果排除它将使用realmName作为存储库名称。

设置该演示使用AppHost Configure方法静态配置存储库:

public override void Configure(Funq.Container container)
{
    container.Register<MyUserRepository[]>(c => new[] 
    { 
        new MyUserRepository("Simpsons", new Dictionary<string, string> {
            { "cburns", "excellent" },
            { "bartsimpson", "Ay caramba" },
            { "homersimpson", "donuts" }
        }), 
        new MyUserRepository("Superheros", new Dictionary<string, string> {
            { "thehulk", "pebbles" },
            { "captainamerica", "redwhiteblue" },
            { "spiderman", "withgreatpower" }
        })
    });
}

测试:当您导航到/TheSimpsons时,服务会提示输入“辛普森一家”凭据,但此处不会允许“超级英雄”凭据。当你去/Superheros时,情况正好相反。


我感谢此解决方案确实偏离了ServiceStack身份验证提供程序。如上所述,可以使用ServiceStack身份验证提供程序从头开始构建您自己的身份验证,但这将很困难,超出了StackOverflow的范围。如果要执行此操作,可以通读现有提供程序并确定如何完成此操作。但是我的建议是,如果你不需要那么复杂,那么请从上面的例子开始。