ASP.NET MVC - HTTP身份验证提示

27

我的应用程序是否可以在渲染视图之前提示输入用户名和密码来获取信息,就像Twitter API获取有关您帐户的信息一样:

http://twitter.com/account/verify_credentials.xml

因此,在渲染视图或文件之前,它会要求您插入用户名和密码。我认为这是直接在服务器上完成的,因为curl请求也是基于用户名:密码的,就像这样:

curl -u user:password http://twitter.com/account/verify_credentials.xml

我正在尝试构建一个API,遵循与Ruby on Rails相同的结构。请问在ASP.NET MVC C#中如何实现这一点?我已经在Ruby on Rails中使用过这个功能,并且很简单,像这样:

before_filter :authenticate

def authenticate
    authenticate_or_request_with_http_basic do |username, password|
    username == "foo" && password == "bar"
end

我认为[Authorize] 过滤器并不相同,因为我相信它只是一个重定向,会将您重定向到基于账户数据库的Accounts Internal Controller,在这种情况下,我将使用来自 Web 服务的另一个数据库,并在提交信息后进行验证。但我需要要求该操作要求用户和密码凭据。

提前致谢


更新:

实际上,要请求需要身份验证的页面(例如 Twitter),我需要在其请求中声明此内容。

request.Credentials = new NetworkCredential("username", "password");

这将反映出提示的用户名和密码。

因此,如果可能在请求时提供身份验证提示信息,那么如何在请求中要求进行身份验证呢?

所以每次有人尝试访问我的应用程序(例如:)

http://myapplication/clients/verify_credentials

它应该要求输入用户名和密码与服务器提示一起使用, 例如,在curl上检索信息就像这样:

curl -u user:password http://myapplication/clients/verify_credentials

应用程序也使用表单身份验证吗? - Çağdaş Tekin
实际上,表单将使用我正在构建的外部接口,因此所有验证和其他操作都应该像 Twitter 一样在请求中进行,而不需要任何图形界面。在我的控制器内部,我将执行验证操作。我会在我的问题中更新更多细节。 - zanona
4个回答

49

要求基本身份验证,您需要返回401状态代码。但这样做会导致当前身份验证模块执行其默认的未经授权处理程序(对于表单身份验证,这意味着重定向到登录页面)。

我编写了一个ActionFilterAttribte,以查看当在web.config中没有安装身份验证模块时是否可以获得所需的行为。

public class RequireBasicAuthentication : ActionFilterAttribute {
   public override void OnActionExecuting(ActionExecutingContext filterContext) {
       var req = filterContext.HttpContext.Request;
       if (String.IsNullOrEmpty(req.Headers["Authorization"])) {
           var res = filterContext.HttpContext.Response;
           res.StatusCode = 401;
           res.AddHeader("WWW-Authenticate", "Basic realm=\"Twitter\"");
           res.End();
       }
   }
}

控制器的动作:

[RequireBasicAuthentication]
public ActionResult Index() {
    var cred = System.Text.ASCIIEncoding.ASCII
            .GetString(Convert.FromBase64String(
            Request.Headers["Authorization"].Substring(6)))
            .Split(':');
    var user = new { Name = cred[0], Pass = cred[1] };
    return Content(String.Format("user:{0}, password:{1}", 
        user.Name, user.Pass));
}

这个操作成功地打印了我输入的用户名和密码。但我真的怀疑这是最好的方法吗?难道没有别的选择来获取用户名和密码吗?


是的,非常感谢您的帮助Çağdaş,但是很遗憾,当涉及到Windows平台时,事情会变得更加复杂,至少目前我可以这么说,因为我不是这方面的专家。但是这样做行不通,因为请求本身不需要用户名和密码,这将表现为简单的重定向,使用curl访问此URL将导致以下结果:验证或不验证:<html><head><title>Object moved</title></head><body> <h2>Object moved to <a href="%2fAccount%2fLogOn%3fReturnUrl%3d%252fclient%252fverify">here</a></h2></body></html> :( - zanona
让我思考的是,是否真的有可能基于ASP.NET通过URL调用构建REST API,但不幸的是似乎不可能。 - zanona
只有在启用表单身份验证时,才会进行重定向。我已经测试了从web.config中删除authentication部分,并且它要求输入用户名和密码而不是重定向。 - Çağdaş Tekin
我实现了ActionFilter和Http身份验证,但我无法检索Request.Header["Authorization"],因为我没有或看不到这样的标头。 - mhesabi

3

根据我阅读的内容,您想创建一个服务而不是基于Web的应用程序。我猜测您选择ASP.NET MVC是为了利用路由和构建所需URL的方式?如果我猜错了,请纠正我。

在我看来,如果您要返回数据,则解决您遇到的问题的最佳方法是使用WCF构建RESTful Web服务。如果您想沿着这条路走,文章可以帮助您入门。

否则,您需要更进一步处理请求并进行身份验证。如果是这种情况,我可以提供更多信息和代码来帮助您。


嗨Dale,实际上我认为你的答案非常好,是的,我正在尝试使用MVC创建这个API,因为它很方便,而且主要应用程序也驻留在其中,所以这个API将成为在其他平台上使用的扩展,比如桌面应用程序、手机应用等。我对WCF一无所知,但它似乎非常好,我相信值得一试。 它似乎比创建一个非常复杂的结构来执行一个简单的任务要好得多。 再次感谢Dale。 - zanona
今天我们有ASP.NET WEB API,很快甚至会与MVC合并成一个框架。 - Alex

3

我修改了çağdaş的答案,将整个逻辑放在了我的自定义ActionFilter属性中。

public class BasicAuthenticationAttribute : ActionFilterAttribute
{
    public string BasicRealm { get; set; }
    protected string Username { get; set; }
    protected string Password { get; set; }

    public BasicAuthenticationAttribute(string username, string password)
    {
        this.Username = username;
        this.Password = password;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var req = filterContext.HttpContext.Request;
        var auth = req.Headers["Authorization"];
        if (!String.IsNullOrEmpty(auth))
        {
            var cred = System.Text.ASCIIEncoding.ASCII.GetString(Convert.FromBase64String(auth.Substring(6))).Split(':');
            var user = new { Name = cred[0], Pass = cred[1] };
            if (user.Name == Username && user.Pass == Password) return;
        }
        var res = filterContext.HttpContext.Response;
        res.StatusCode = 401;
        res.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
        res.End();
    }
}

它可以用于在整个控制器下放置基本身份验证:

[BasicAuthenticationAttribute("your-username", "your-password", 
    BasicRealm = "your-realm")]
public class HomeController : BaseController
{
   ...
}

或特定的ActionResult:

public class HomeController : BaseController
{
    [BasicAuthenticationAttribute("your-username", "your-password", 
        BasicRealm = "your-realm")]
    public ActionResult Index() 
    {
        ...
    }
}

注意:以上实现需要开发人员手动插入用户名和密码作为ActionFilter所需参数,但可以轻松扩展以支持任何授权机制(MembershipProvider、ASP.NET Identity、外部DBMS或文件上的自定义用户库等),方法是删除自定义构造函数并相应修改OnActionExecuting方法中的IF块。

如需更多信息,您也可以阅读我在博客上写的这篇文章


谢谢,非常适合快速解决问题! - Peter
你甚至可以通过派生自AuthorizeAttribute并重写AuthorizeCore函数来进一步改进它。这样,如果身份验证失败,Action就不会被调用。此外,它会在任何其他属性之前被调用。在这里查看更多信息:https://dev59.com/ZG435IYBdhLWcg3w1jyV - TS.

2
这是我使用过的方法,虽然需要一些操作,但能让IIS和MVC3更像其他基本HTTP身份验证系统(如Apache)...
步骤1:
确保在IIS中安装了“基本身份验证”。
(例如:控制面板->程序和功能->打开或关闭Windows功能)
*我目前使用的是Windows 7,不确定路径是否相同。[GOOGLE:在IIS中安装基本身份验证]应该可以帮到你。
步骤2:
确保在您的站点下启用了基本身份验证。如果您在上一步中需要安装,则需要确保重置IIS服务并使所有应用程序池实际关闭。
步骤3:
(注意:我使用的是MVC3,并认为这应该适用于大多数模型,包括ASP.Net,无需大量努力。) 在项目中,您需要添加以下类:
public class ServicePrincipal : IPrincipal { // This answers the "What am I allowed to do" question

  // In real life, this guy will contain all your user info
  // and you can put what ever you like and retrieve it 
  // later via the HttpContext, on your application side.
  // Some fun with casting will be required.

  public static IPrincipal Default { 
    get {
      return new ServicePrincipal {
        Identity = new ServiceIdentity {
          AuthenticationType = "Test",
          IsAuthenticated = true,
          Name = "Basic"
        }
      };
    }
  }

  public IIdentity Identity { get; set; } 

  public bool IsInRole(string role) {
    // If you want to use role based authorization
    // e.g. [Authorize(Roles = "CoolPeople")]
    // This is the place to do it and you can do
    // anything from load info from a db or flat file
    // or simple case statement...though that would 
    // be silly.
    return true;
  }
}

public class ServiceIdentity : IIdentity { // This answers the "Who Am I" Question
  public string AuthenticationType { get; set; }

  public bool IsAuthenticated { get; set; }

  public string Name { get; set; }
}


public class ServiceModule : IHttpModule { // This is the module for IIS
  public void Init(HttpApplication context) {
    context.AuthenticateRequest += this.BasicAuthenticationRequest;
  }

  public void BasicAuthenticationRequest(object sender, EventArgs e) {
    HttpApplication app = sender as HttpApplication;

    if( !ServiceProvider.Authenticate(app.Context) ) {
      // Total FAIL!
    }
  }

  public void Dispose() {
    // Clean up the mess, if needed.
  }

}

public class ServiceProvider {

  public static bool Authenticate( HttpContext context ) {
    // For the example we are going to create a nothing user
    // say he is awesome, pass him along through and be done.
    // The heavy lifting of the auth process will go here 
    // in the real world.

    HttpContext.Current.User = ServicePrincipal.Default;
    return true;
  }  
}

第三步a. [编辑]

以下是你将要“使用”的不同库

using System.Security.Principal;
using System.Web;

只是想把它们扔进去。我讨厌人们忽略它们。:)

第四步。

将以下内容添加到您的Web配置文件中。请注意,我包括周围的结构,例如“configuration”标记...这只是一张路线图,如果您已经有一个“configuration”标记,请不要添加其他标记,否则IIS会对您感到不满。

<configuration>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="ServiceCredentialModule" type="{Namespace}.ServiceModule"/>
    </modules>
  </system.webServer>
<configuration>

请注意,{Namespace}.ServiceModule 中的命名空间是您将第3步中的类放入的命名空间。
...就是这样。

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