ASP.NET MVC 5中的基本身份验证

46

实现 ASP.NET MVC 5 中基本认证需要哪些步骤?

我了解到OWIN不支持无Cookie的身份验证,那么基本认证是否通常可行?

我是否需要一个自定义属性?我不确定这些属性是如何工作的。


1
有时候解决方案可能在其他问题中,有人已经在stackoverflow上完成了它,这里有完整的代码:https://dev59.com/BV_Va4cB1Zd3GeqPW8kG#9048151 - user3203465
Cookies和身份验证并不相关。一个可以使用另一个,但两者都不依赖于另一个。 - Erik Philips
9个回答

87

您可以使用自定义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;
        }
        filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
        /// thanks to eismanpat for this line: http://www.ryadel.com/en/http-basic-authentication-asp-net-mvc-using-custom-actionfilter/#comment-2507605761
        filterContext.Result = new HttpUnauthorizedResult();
    }
}

可以将整个控制器放在基本身份验证下使用:

[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() 
    {
        ...
    }
}

如果您需要更多信息,请查看我在该主题下撰写的这篇博客文章


1
这种技术对我来说非常有效,即使是混合身份验证,例如在同一个网站中同时使用基本身份验证和表单身份验证。 - Anil Vangari
7
提醒一句,这个属性在Web API中不起作用,你最好使用过滤器 - http://weblog.west-wind.com/posts/2013/Apr/18/A-WebAPI-Basic-Authentication-Authorization-Filter - Henry C
5
太棒了,这正是我所需要的! - irhetoric
2
@MacakM:对于所有发出挑战的身份验证方案,都需要realm属性(不区分大小写)。 realm值(区分大小写)与正在访问的服务器的规范根URL结合使用,定义了保护空间。这些领域允许将服务器上受保护的资源划分为一组保护空间,每个保护空间都有自己的身份验证方案和/或授权数据库。[来自RFC 1945(HTTP / 1.0)和RFC 2617] - Darkseal
5
将基本身份验证与表单身份验证结合使用可能会导致问题:基本身份验证的 401 响应会重定向到表单身份验证登录页面。为了避免这种情况,只需在 filterContext.Result 之前添加以下代码行:filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;,您需要 .NET 4.5 或更高版本。 - Spilarix
显示剩余8条评论

13

您可以使用自定义属性来实现此功能。在开源项目SimpleSecurity中,有一个支持基本身份验证的自定义属性实现,您可以在此处下载。有一个参考应用程序以演示如何使用它。它最初是为与MVC 4中的SimpleMembership配合使用而开发的,并最近已经移植到使用MVC 5中的ASP.NET Identity


9
我想修改Darkseal分享的答案,因为该代码存在一个重大的安全漏洞。如其所写,当调用res.End()时,该操作过滤器实际上并未终止请求。如果凭据不匹配,则会提示用户输入凭据并返回401响应,但控制器动作仍在服务器端执行。您需要将filterContext.Result属性设置为某些内容,以使请求正确终止并且不继续到操作方法中。
对于我的情况尤其糟糕,因为我正在尝试保护从第三方接收数据源的Web服务端点。如其所写,此操作过滤器并未保护任何内容,因为数据仍通过我的操作方法推送。
我的“快速修复”如下:
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.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
        filterContext.Result = new HttpUnauthorizedResult();
    }
}

2
“快速修复”已经由@Darkseal在主答案中应用,res.end()的问题已被替换为HttpUnauthorizedResult() - starlocke
1
似乎添加 filterContext.Result = new HttpUnauthorizedResult(); 会导致 ASP 将用户重定向到默认的登录页面,而不是让身份验证弹出窗口出现。 - Douglas Gaskell

4

@Darkseal的回答很好。以下是相同的代码,用于与ASP.NET Web API(MVC的近亲)一起使用。相同的思路,稍微不同的命名空间和上下文类。以完全相同的方式将其添加到您的类和方法中。

using System.Web.Http.Controllers;
using System.Web.Http.Filters;

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)
    {
        Username = username;
        Password = password;
    }

    public override void OnActionExecuting(HttpActionContext filterContext)
    {
        var req = filterContext.Request;
        var auth = req.Headers.Authorization;
        if (auth?.Scheme == "Basic")
        {
            var cred = Encoding.ASCII.GetString(Convert.FromBase64String(auth.Parameter)).Split(':');
            var user = new { Name = cred[0], Pass = cred[1] };
            if (user.Name == Username && user.Pass == Password) return;
        }
        filterContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
        filterContext.Response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", BasicRealm ?? "YourRealmName"));
    }
}

请参考以下链接中的类似答案,了解如何在WebAPI中使用AuthorizationFilterAttribute和WebClient库。 - Michael Freidgeim

3
HTTP基本认证不需要cookie,它是基于HTTP请求中的一个头部信息进行的。该头部信息名为“Authorization”,其值应为用户名和密码以"用户名:密码"的形式组合成字符串(均使用base64编码)。
我并未在ASP.NET MVC中使用过基本认证,但我使用了Web API来创建自定义属性(你可以从这里开始学习Web API:链接,或者从MVC中学习:链接)。

4
但对于一个Mvc应用程序,你需要存储在Cookie中。例如每个请求都不会自动附加标头。客户端浏览器需要添加授权标头,但它不会这样做。WebApi不同,我们可以控制HttpClient。 - harsimranb
@harsimranb 自90年代中期以来,没有一个主流浏览器没有不断添加授权头。你可能在服务器端有一个错误。 - Jon Hanna
@harsimranb WebApi并不不同;客户端浏览器总是会添加授权头,将其存储在cookie中与MVC无关,并且这是一种选择。 - Zimano

1
你可以在Nuget上尝试这个包(AuthPackage),它可以让你轻松地为你的asp.net mvc添加身份验证。
  1. install package using Package Manager Console:

    Install-Package AuthPackage

  2. add Connection String to your Web.config in (appSettings):

     <add key="connectionString" value="connectionStringHere" />
    
  3. you're ready to register users, login, logout

例子:
 public async Task<ActionResult> SignIn()
    {
        var context = System.Web.HttpContext.Current;
        AuthUser authUser = new AuthUser(context);
        await authUser.SignIn("waleedchayeb2@gmail.com", "123456");
        return RedirectToAction("Index", "Home");
    }

你可以在这里阅读文档here

你应该提到你是这个包的作者。 - Michael Freidgeim

1

这是 Darkseal's answer

[BasicAuthenticationAttribute("your-username", "your-password", 
    BasicRealm = "your-realm")]

有两个缺点:用户名和密码是硬编码的,只支持单个用户。

更灵活的解决方案应该支持在配置中存储多个用户名/密码对。

微软描述了一个示例 https://gm/aspnet/samples/tree/main/samples/aspnet/WebApi/BasicAuthentication。

public abstract class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter

在过载中

abstract Task<IPrincipal> AuthenticateAsync(string userName, string password,   
CancellationToken cancellationToken);   

你可以实现检查,以查找标题中的用户名/密码是否存在于配置/秘密的用户名/密码对列表中。
还可以创建执行基本身份验证的HTTP模块。您可以通过替换CheckPassword方法轻松地插入ASP.NET成员资格提供程序。 https://learn.microsoft.com/en-us/aspnet/web-api/overview/security/basic-authentication#basic-authentication-with-custom-membership OWIN实现示例 https://github.com/scottbrady91/Blog-Example-Classes/tree/master/OwinBasicAuthentication/WebApi 在.Net Core中的可能实现描述在 https://github.com/mihirdilip/aspnetcore-authentication-basic

0
我们的一个应用程序“意外地”使用了基本身份验证,因为在Web.config中有以下代码:
<system.webServer>
    <modules>
        <remove name="FormsAuthentication" />
    </modules>
    ... other stuff
</system.webServer>

该应用程序已经配置以使用表单身份验证。每当正常的表单认证将被使用时,浏览器身份验证窗口就会弹出。


0
这是对接受答案的微小变化。我的情景是,我有一个作为API和返回“您已成功连接到服务器”的页面的Asp.net(4.5.2)控制器。这样做的目的是为了在尝试将其与API一起使用之前测试基本身份验证是否正常工作。请注意,这是一个遗留应用程序,要求规定使用基本身份验证,这就是为什么仍然需要它的原因。
问题是,网站的其余部分都使用FormsAuthentication进行设置,因此当BasicAuthenticationFilter运行时,它会将结果从401未经授权更改为302重定向,用户最终会进入登录页面。
我真正想要的是在浏览器中显示基本身份验证弹窗。再次强调,这仅是为了让最终用户测试凭据。这是我必须更改的内容才能使其正常工作:
    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;
          }

     filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", BasicRealm ?? "Ryadel"));
     filterContext.HttpContext.Response.StatusCode = 401;
     filterContext.HttpContext.Response.End();
}

请注意使用这行代码:
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;

没有实际抑制表单重定向(不知道为什么,感觉应该可以)。
  [HttpGet]
  [BasicAuthentication("TestUserName", "Abc123*")]
  public ActionResult Submit()
  {
      return new ContentResult() { Content = "You have successfully connected to the AP&G GISB Server." };
  }

请注意,实际的实现会在数据库中检查用户名和密码,上述只是演示代码。

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