c# - 具有多个存储库的 ServiceStack 身份验证

标签 c# .net authentication servicestack

我们可以为服务堆栈设置多个 AuthProvider。但它们都应该使用一个用户存储库。有没有一种方法可以为不同的路由设置一个基本身份验证提供程序和两个不同的存储库?

最佳答案

拒绝开箱即用的多个存储库:

简短的回答是您不能简单地使用开箱即用的多个存储库。它不支持这一点的原因是因为 CredentialsProvider 使用 AppHost 依赖项容器(IoC) 来解析 IUserAuthRepository,因此它只期望一个存储库,此外,它还需要有关从路由中使用哪个存储库的更多信息

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

// 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具有很强的可扩展性,您可以轻松地扮演自己的身份验证角色。基本身份验证是一个非常简单的协议(protocol)。

我只是创建了这个 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(字符串 realmName, 字符串 repositoryName)]

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

设置演示在AppHostConfigure方法中静态配置存储库:

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 时,服务将提示输入“Simpsons”凭据,但此处不允许使用“Superheros”凭据。当您转到 /Superheros 时,情况正好相反。


我意识到这个解决方案确实偏离了 ServiceStack 身份验证提供程序。如前所述,可以使用 ServiceStack 身份验证提供程序从头开始构建您自己的身份验证,但这会很困难,并且超出了 StackOverflow 的范围。如果您想这样做,您可以通读现有的提供程序并确定如何完成此操作。不过我的建议是,如果您不需要那么复杂,那么就从上面的示例开始工作。

关于c# - 具有多个存储库的 ServiceStack 身份验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21297384/

相关文章:

c# - C# 中的设计时间序列化

.net - 在 Mono 和 Visual Studio 之间共享源代码

Azure PowerBI 身份验证,无需重定向到另一个页面

php - 安全登录表单,或者至少我希望如此

authentication - SSL 握手中的客户端证书身份验证

c# - 如何使用 C# 计算 DataTable 列的总和(总计)

c# - 尝试在 Swift 中复制 C# POST 调用

c# - 我从哪里开始使用 C#?

.net - 在不使用 Office Automation 的情况下生成 PowerPoint 演示文稿的一些技术有哪些?

c# - 即使不在授权集中,反射也不受限制