如何从头部检索基本身份验证凭据?

87

我正在尝试编写一些简单的测试代码,验证基本身份验证机制下的用户身份验证。如何从标头(header)中检索到凭证?

string authorizationHeader = this.HttpContext.Request.Headers["Authorization"];

我现在该怎么办?有很多教程,但我对.NET和身份验证都很陌生,你能在回答中详细解释每一个步骤的目的和原因吗?

4个回答

270

来自我的博客:

这将详细解释所有工作原理:

步骤1 - 理解基本认证

每当您使用基本认证时,都会向HTTP请求添加一个头,它看起来类似于:

Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

来源:http://en.wikipedia.org/wiki/Basic_access_authentication

"QWxhZGRpbjpvcGVuIHNlc2FtZQ=="只是以Base64编码的"用户名:密码"(http://en.wikipedia.org/wiki/Base64)。为了访问.NET(C#)中的标头和其他HTTP属性,您需要访问当前的Http上下文:

HttpContext httpContext = HttpContext.Current;

您可以在System.Web命名空间中找到此内容。

步骤2 - 获取标头

授权标头不是HttpContext中唯一的标头。为了访问标头,我们需要从请求中获取它。

string authHeader = this.httpContext.Request.Headers["Authorization"];

(或者您可以使用 AuthenticationHeaderValue.TryParse,如下面的 pasx的答案中所建议的

如果您调试代码,您会看到该标头的内容类似于:

Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

步骤3 - 检查标头

您已经提取了标头,现在有几件事情需要做:

  1. 检查标头不为空
  2. 检查授权/身份验证机制确实是“基本”

就像这样:

if (authHeader != null && authHeader.StartsWith("Basic")) {
    //Extract credentials
} else {
    //Handle what happens if that isn't the case
    throw new Exception("The authorization header is either empty or isn't Basic.");
}

现在您已经确认您有一些数据可以提取。

步骤4 - 提取凭证

删除 "Basic " 子字符串

现在,您可以尝试获取用户名和密码的值。首先,您需要摆脱 "Basic " 子字符串。您可以这样做:

string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();

请查看以下链接以获取更多详细信息:

  1. http://msdn.microsoft.com/en-us/library/system.string.substring(v=vs.110).aspx
  2. http://msdn.microsoft.com/en-us/library/t97s7bs3(v=vs.110).aspx

解码Base64

现在我们需要将Base64解码为字符串:

//the coding should be iso or you could use ASCII and UTF-8 decoder
Encoding encoding = Encoding.GetEncoding("iso-8859-1");
string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));

现在用户名和密码将以此格式显示:
username:password

分离用户名和密码

为了获取用户名和密码,我们可以简单地获取“:”的索引。

int seperatorIndex = usernamePassword.IndexOf(':');

username = usernamePassword.Substring(0, seperatorIndex);
password = usernamePassword.Substring(seperatorIndex + 1);

现在你可以使用这些数据进行测试。

最终代码

最终的代码可能如下所示:

HttpContext httpContext = HttpContext.Current;

string authHeader = this.httpContext.Request.Headers["Authorization"];

if (authHeader != null && authHeader.StartsWith("Basic")) {
    string encodedUsernamePassword = authHeader.Substring("Basic ".Length).Trim();
    Encoding encoding = Encoding.GetEncoding("iso-8859-1");
    string usernamePassword = encoding.GetString(Convert.FromBase64String(encodedUsernamePassword));

    int seperatorIndex = usernamePassword.IndexOf(':');

    var username = usernamePassword.Substring(0, seperatorIndex);
    var password = usernamePassword.Substring(seperatorIndex + 1);
} else {
    //Handle what happens if that isn't the case
    throw new Exception("The authorization header is either empty or isn't Basic.");
}

2
完美的答案,但请注意,如果用户名或密码包含“:”,则此解决方案将无法工作。 - Roman Marusyk
13
IndexOf 应该选择字符的第一个出现位置。因此,根据 RFC 7617 明确指出“另外,包含冒号字符的用户ID是无效的,因为用户密码字符串中的第一个冒号分隔了用户ID和密码”,所以您的用户名不能包含冒号。 - Dawid O

25

除了主要答案,摆脱“Basic”子字符串的最佳方法是使用AuthenticationHeaderValue Class

var header = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
var credentials = header.Parameter;

如果标头内容无效,例如:"Basic"部分不存在,则会抛出FormatException异常。

或者,如果您不想引发异常,请使用AuthenticationHeaderValue.TryParse


这非常有用,因为它适用于NET 4.5+,并解析令牌参数和方案(Basic、Bearer等)。+1 - Yogi

4

来自@DawidO的精彩回答。

如果你只是想提取基本身份验证凭据,并依靠.NET魔法,假设你有HttpContext,那么这也可以工作:

  public static void StartListener() {
    using (var hl = new HttpListener()) {
      hl.Prefixes.Add("http://+:8008/");
      hl.AuthenticationSchemes = AuthenticationSchemes.Basic;
      hl.Start();
      Console.WriteLine("Listening...");
      while (true) {
        var hlc = hl.GetContext();

        var hlbi = (HttpListenerBasicIdentity)hlc.User.Identity;
        Console.WriteLine(hlbi.Name);
        Console.WriteLine(hlbi.Password);

        //TODO: validater user
        //TODO: take action
      }
    }
  }

1
请注意,使用这种方法将被限制为使用 iso-8859-1 字符集进行解码。如果您的数据实际上是以 utf-8 编码的,那么您将会得到意想不到的结果。 - Matthew

1

记住,使用字符串可能不太安全。它们会一直存储在内存中,直到被垃圾回收器清除。


你能详细说明为什么这是一个安全问题吗? - bombek
1
@bombek 我相信答案建议使用 SecureString https://learn.microsoft.com/en-us/dotnet/api/system.security.securestring?view=net-5.0。但这种方法现在已经不被推荐,详情请见 https://github.com/dotnet/platform-compat/blob/master/docs/DE0001.md - Michael Freidgeim

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