使用基本HTTP身份验证的MVC4 app + WebApi中的SimpleMembership

时间:2013-02-11 19:20:59

标签: asp.net-mvc-4 asp.net-web-api simplemembership

我正在尝试实现具有以下要求的MVC4 Web应用程序:

(a)它仅向经过身份验证的用户提供服务。至于身份验证,我想使用简单的成员身份,因为它是MVC的最新身份验证技术,使我能够定义自己的数据库表,提供开箱即用的OAuth支持,并且可以轻松地与MVC和的WebAPI。

(b)它通过WebApi为移动/ JS客户端公开了一些核心功能,这些功能应该通过基本的HTTP身份验证(+ SSL)进行身份验证。通常情况下,我会让JS客户端使用对WebApi控制器的jQuery AJAX调用,并使用Authorize属性为不同的用户角色进行修饰。

(c)理想情况下,在混合环境中我想避免双重身份验证:即如果用户已通过浏览器进行身份验证,并且正在访问暗示对WebApi控制器操作进行JS调用的页面,则(a)机制应该足够了。

因此,虽然(a)由默认的MVC模板覆盖,但(b)需要基本的HTTP身份验证而不需要浏览器的中介。为此,我应该创建一个DelegatingHandler,就像我在这篇文章中找到的那样:http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers。 问题是它的实现需要某种方法从收到的用户名和密码中检索IPrincipal,而WebSecurity类没有为此提供任何方法(登录除外,但我会避免仅为了授权而更改记录的用户) ,也因为潜在的“混合”环境,如(c))。因此,似乎我唯一的选择是放弃简单的会员资格。有没有人有更好的建议?以下是引用帖子中的相关(略微修改)代码:

public interface IPrincipalProvider
{
    IPrincipal GetPrincipal(string username, string password);
}

public sealed class Credentials
{
    public string Username { get; set; }
    public string Password { get; set; }
}

public class BasicAuthMessageHandler : DelegatingHandler
{
    private const string BasicAuthResponseHeader = "WWW-Authenticate";
    private const string BasicAuthResponseHeaderValue = "Basic";

    public IPrincipalProvider PrincipalProvider { get; private set; }

    public BasicAuthMessageHandler(IPrincipalProvider provider)
    {
        if (provider == null) throw new ArgumentNullException("provider");
        PrincipalProvider = provider;
    }

    private static Credentials ParseAuthorizationHeader(string sHeader)
    {
        string[] credentials = Encoding.ASCII.GetString(
            Convert.FromBase64String(sHeader)).Split(new[] { ':' });

        if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) ||
            String.IsNullOrEmpty(credentials[1])) return null;

        return new Credentials
        {
            Username = credentials[0],
            Password = credentials[1],
        };
    }

    protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        AuthenticationHeaderValue authValue = request.Headers.Authorization;
        if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter))
        {
            Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
            if (parsedCredentials != null)
            {
                Thread.CurrentPrincipal = PrincipalProvider
                    .GetPrincipal(parsedCredentials.Username, parsedCredentials.Password);
            } 
        } 

        return base.SendAsync(request, cancellationToken)
            .ContinueWith(task =>
            {
                var response = task.Result;
                if (response.StatusCode == HttpStatusCode.Unauthorized
                    && !response.Headers.Contains(BasicAuthResponseHeader))
                {
                    response.Headers.Add(BasicAuthResponseHeader,
                        BasicAuthResponseHeaderValue);
                } 
                return response;
            });
    }
}

3 个答案:

答案 0 :(得分:3)

谢谢,这似乎是目前最好的解决方案! 我设法从头开始创建一个虚拟解决方案(在此处找到它:http://sdrv.ms/YpkRcf),它似乎适用于以下情况:

1)当我尝试访问MVC控制器限制操作时,我会按预期重定向到登录页面以进行身份​​验证。

2)当我触发对WebApi控制器限制动作的jQuery ajax调用时,调用成功(除非当然不使用SSL)。

然而,当在网站登录后,它仍无法运行,API调用仍然需要身份验证。有人能解释一下这里会发生什么吗?在下文中,我详细介绍了我的程序,因为我认为它对像我这样的初学者可能有用。

谢谢(抱歉格式化了以下内容,但我无法让这个编辑器正确标记代码......)


程序

  1. 创建一个新的mvc4应用程序(基本的mvc4应用程序:这已经是通用提供程序。所有通用提供程序类名称都以Default ...开头;)

  2. 为您的非本地数据库自定义web.config,例如:

      <connectionStrings>
    <add name="DefaultConnection"
     providerName="System.Data.SqlClient"
     connectionString="data source=(local)\SQLExpress;Initial Catalog=Test;Integrated Security=True;MultipleActiveResultSets=True" />
    

  3. 为哈希密码设置machineKey通常也很有用,这样您就可以自由地将此站点从服务器移动到服务器而无需对密码进行加密。使用机器密钥生成器网站定义如下条目:

      <system.web>
            <machineKey
       validationKey="...thekey..."
       decryptionKey="...thekey..."
       validation="SHA1"
       decryption="AES" />
    
    1. 如果需要,创建一个与web.config的连接字符串对应的新的空数据库。然后启动我们好老的朋友WSAT(来自VS Project菜单)并根据需要添加用户和角色来配置安全性。

    2. 如果您愿意,可以添加一个带有索引操作的HomeController,因为此模板中不存在控制器,因此如果没有控制器,您就无法测试启动Web应用程序。

    3. 从NuGet添加Thinktecture.IdentityModel.45并添加/更新所有您喜欢的NuGet包。请注意,在编写本文时,来自MS的不引人注意的jquery验证与jQuery 1.9或更高版本不兼容。我宁愿使用http://plugins.jquery.com/winf.unobtrusive-ajax/。因此,删除jquery.unobtrusive *并在您的包中添加此库(由winf.unobtrusive-ajax和additional-methods组成)(App_Start / BundleConfig.cs)。

    4. 通过在DefaultApi路由配置之后添加代码来修改App_Start中的WebApiConfig.cs:

      public static class WebApiConfig {     public static void Register(HttpConfiguration config)     {         config.Routes.MapHttpRoute(             名称:&#34; DefaultApi&#34;,             routeTemplate:&#34; api / {controller} / {id}&#34;,             默认值:new {id = RouteParameter.Optional}         );

          // added for Thinktecture
          var authConfig = new AuthenticationConfiguration
          {
              InheritHostClientIdentity = true,
              ClaimsAuthenticationManager = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager
          };
      
          // setup authentication against membership
          authConfig.AddBasicAuthentication((userName, password) => Membership.ValidateUser(userName, password));
      
          config.MessageHandlers.Add(new AuthenticationHandler(authConfig));
      }
      

      }

    5. 为了更清洁,api控制器将被置于Controllers / Api /下,因此创建此文件夹。

      1. 将LoginModel.cs添加到模型中:

        公共类LoginModel {     [需要]     [显示(名称=&#34;用户名&#34;,ResourceType = typeof(StringResources))]     public string UserName {get;组; }

        [Required]
        [DataType(DataType.Password)]
        [Display(Name = "Password", ResourceType = typeof(StringResources))]
        public string Password { get; set; }
        
        [Display(Name = "RememberMe", ResourceType = typeof(StringResources))]
        public bool RememberMe { get; set; }
        

        }

      2. 此模型需要一个StringResources.resx资源(带代码生成)我通常放在Assets文件夹下,属性中引用了3个字符串。

        1. 将ClaimsTransformer.cs添加到您的解决方案根目录,如下所示:

          public class ClaimsTransformer:ClaimsAuthenticationManager {     public override ClaimsPrincipal Authenticate(string resourceName,ClaimsPrincipal incomingPrincipal)     {         if(!incomingPrincipal.Identity.IsAuthenticated)         {             return base.Authenticate(resourceName,incomingPrincipal);         }

              var name = incomingPrincipal.Identity.Name;
          
              return Principal.Create(
                  "Custom", 
                  new Claim(ClaimTypes.Name, name + " (transformed)"));
          }
          

          }

        2. 将Application_PostAuthenticateRequest添加到Global.asax.cs:

          public class MvcApplication:HttpApplication {     ...     protected void Application_PostAuthenticateRequest()     {         if(ClaimsPrincipal.Current.Identity.IsAuthenticated)         {             var transformer = FederatedAuthentication.FederationConfiguration.IdentityConfiguration.ClaimsAuthenticationManager;             var newPrincipal = transformer.Authenticate(string.Empty,ClaimsPrincipal.Current);

                  Thread.CurrentPrincipal = newPrincipal;
                  HttpContext.Current.User = newPrincipal;
              }
          }
          

          }

        3. web.config(用你的app root命名空间替换YourAppNamespace):

          &LT; configSections&GT; &lt; section name =&#34; system.identityModel&#34; type =&#34; System.IdentityModel.Configuration.SystemIdentityModelSection,System.IdentityModel,Version = 4.0.0.0,Culture = neutral,PublicKeyToken = B77A5C561934E089&#34; /&GT; ...

        4. 为帐户控制器添加其他模型及其视图(您可以从MVC3应用程序模板中获取它们,即使我更喜欢使用需要字符串资源名称而不是文字的属性将它们更改为更易于本地化的变体)。

        5. 测试基于浏览器的身份验证,将一些[授权]操作添加到控制器(例如HomeController),然后尝试访问它。

        6. 测试基本的HTTP身份验证,在某些视图(例如Home / Index)中插入这样的代码(在令牌变量中设置您的用户名和密码):

          ...

          &lt; p&gt;测试电话

              $(function(){         $(&#34; #test&#34;)。click(function(){             var token =&#34; USERNAME:PASSWORD&#34 ;;             var hash = $ .base64.encode(token);             var header =&#34; Basic&#34; +哈希;             的console.log(报头);
                  $.ajax({
                      url: "/api/dummy",
                      dataType: "json",
                      beforeSend: function(xhr) {
                          xhr.setRequestHeader("Authorization", header);
                      },
                      success: function(data) {
                          alert(data);
                      },
                      error: function(jqXHR, textStatus, errorThrown) {
                          alert(errorThrown);
                      }
                  });
              });
          });
          
        7. 这需要用于编码/解码Base64的jQuery插件:jquery.base64.js及其缩小的对应物。

          要允许SSL,请按照此处的说明操作:http://www.hanselman.com/blog/WorkingWithSSLAtDevelopmentTimeIsEasierWithIISExpress.aspx(基本上,在Web项目属性中启用SSL并连接到属性值中指定的端口)。

答案 1 :(得分:3)

Here is another solution that meets all of your requirements。它使用SimpleMemberhsip,在MVC 4应用程序中混合使用表单身份验证和基本身份验证。它也可以支持授权,但不需要将Role属性保留为null。

答案 2 :(得分:0)