使用Membership Provider进行ASP.NET MVC 4 Web API身份验证

47
我有一个使用Web API的ASP.NET MVC 4项目。在控制器上,我已经设置了类需要使用[Authorize]属性进行授权。对于身份验证,我正在使用ASP.NET Membership Provider,并且我的Web.Config设置为使用“Forms”身份验证。这里是我卡住的地方:
一切都很顺利,直到我完成了对API的测试,并且想要使用[Authorize]属性来保护控制器,以便我可以开始针对我的Membership Provider中的用户进行身份验证测试。因此,我启动了Fiddler,并进行了相同的调用,同时添加Authorization:Basic属性和来自我的Membership Provider的用户名:密码。

enter image description here

我收到的响应是401未经授权,"Auth"下显示"No WWW-Authenticate Header is present." 然后我意识到API正在寻找一个SHA1编码的密钥。所以我从搜索中启动了一个SHA1生成器,并为我的用户名:密码获取了一个哈希值,并更新了我的请求头,如下所示:

enter image description here

这种方法也不起作用,我得到了相同的结果。显然,我需要一些“共享秘密密钥”来与服务器一起使用,以解码我的用户名/密码。
所以我的问题是:
  1. 如何从服务器(或在这种情况下从运行于VS 2012的Virtual IIS)获取此密钥。
  2. 如何使用它来使用来自ASP.NET成员身份提供程序的用户名/密码进行Fiddler中的身份验证调用。
  3. 我将如何在客户端应用程序中使用它进行相同的调用(C# WPF应用程序)。
  4. 如果与我的HTTP调用结合使用,这是最佳实践吗?如果不是,那是什么?
提前感谢!
1个回答

71
你可以在SSL中使用基本认证。在服务器端,我们可以编写自定义委托处理程序,通过查询我们注册的成员提供程序来验证凭据,并在有效时检索角色并设置当前主体:
public class BasicAuthenticationMessageHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var authHeader = request.Headers.Authorization;

        if (authHeader == null)
        {
            return base.SendAsync(request, cancellationToken);
        }

        if (authHeader.Scheme != "Basic")
        {
            return base.SendAsync(request, cancellationToken);
        }

        var encodedUserPass = authHeader.Parameter.Trim();
        var userPass = Encoding.ASCII.GetString(Convert.FromBase64String(encodedUserPass));
        var parts = userPass.Split(":".ToCharArray());
        var username = parts[0];
        var password = parts[1];

        if (!Membership.ValidateUser(username, password))
        {
            return base.SendAsync(request, cancellationToken);
        }

        var identity = new GenericIdentity(username, "Basic");
        string[] roles = Roles.Provider.GetRolesForUser(username);
        var principal = new GenericPrincipal(identity, roles);
        Thread.CurrentPrincipal = principal;
        if (HttpContext.Current != null)
        {
            HttpContext.Current.User = principal;
        }

        return base.SendAsync(request, cancellationToken);
    }
}

我们将此处理程序在Application_Start中注册:
GlobalConfiguration.Configuration.MessageHandlers.Add(
    new BasicAuthenticationMessageHandler()
);

现在我们可以有一个Api控制器,该控制器将被装饰上[Authorize]属性以确保只有经过身份验证的用户才能访问其操作:
[Authorize]
public class ValuesController : ApiController
{
    public string Get()
    {
        return string.Format("Hello {0}", User.Identity.Name);
    }
}

好的,现在让我们来看一个示例客户端:

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;

class Program
{
    static void Main()
    {
        // since for testing purposes I am using IIS Express
        // with an invalid SSL certificate I need to desactivate
        // the check for this certificate.
        ServicePointManager.ServerCertificateValidationCallback += 
            (sender, certificate, chain, sslPolicyErrors) => true;

        using (var client = new HttpClient())
        {
            var buffer = Encoding.ASCII.GetBytes("john:secret");
            var authHeader = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(buffer));
            client.DefaultRequestHeaders.Authorization = authHeader;
            var task = client.GetAsync("https://localhost:44300/api/values");
            if (task.Result.StatusCode == HttpStatusCode.Unauthorized)
            {
                Console.WriteLine("wrong credentials");
            }
            else
            {
                task.Result.EnsureSuccessStatusCode();
                Console.WriteLine(task.Result.Content.ReadAsAsync<string>().Result);
            }
        }
    }
}

我现在已经开了一个单独的问题:https://dev59.com/Vmct5IYBdhLWcg3whtyh - Slauma
为什么要对 request.Headers.Authorization != null 进行双重验证? - Joel
@Joel,因为那是一个错误。感谢你发现了它。回答已更新。 - Darin Dimitrov
这对我不起作用 :(. 处理程序被浏览器请求调用。但是,当从“代码”调用时,端点会重定向(302)到登录页面,并且处理程序永远不会被调用。 - Chris Arnold
2
嗨,方法:“Membership.ValidateUser()”返回false。我猜我需要设置成员默认数据库连接?如果是这样,我该在哪里设置? - Shaul Zuarets
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接