我需要在ASP.NET Core网站上验证OAuth 1.0a(RFC 5849)请求。不能将客户端升级到OAuth 2.0或其他任何方式。我了解规范,但是为oauth_signature
实施验证过程似乎有些脆弱,并且肯定不需要在这里重新发明轮子。
.NET Core是否有用于处理此问题的内置类?理想情况下,您只需传入HttpRequest
和密钥,并告诉您签名是否有效?
如果没有任何内置功能,那么第三方库上有什么建议可以帮助我解决这个问题?
我真的不愿意通过引入第三方NuGet程序包来依赖它。许多选项提供的功能远远超出了我的需要,并且大多数(不再可以理解)不再处于积极的开发或支持之中。与安全相关的任何事情都受到不支持的“黑匣子”依赖,这对我来说并不正确。
因此,我仅针对需要支持的功能子集(仅进行验证,并将OAuth参数作为表单发布数据传递)进行了自己的实现。这并不是一个完整的实现,但是可以作为发现自己处于类似情况并且不希望引入依赖关系的任何其他人的起点。
最新代码在GitHub上。
public static class OAuth1Utilities
{
private static readonly Lazy<bool[]> UnreservedCharacterMask = new Lazy<bool[]>(CreateUnreservedCharacterMask);
public static string EncodeString(string value)
{
byte[] characterBytes = Encoding.UTF8.GetBytes(value);
StringBuilder encoded = new StringBuilder();
foreach (byte character in characterBytes)
{
if (UnreservedCharacterMask.Value[character])
{
encoded.Append((char)character);
}
else
{
encoded.Append($"%{character:X2}");
}
}
return encoded.ToString();
}
public static string GetBaseStringUri(HttpRequest request)
{
StringBuilder baseStringUri = new StringBuilder();
baseStringUri.Append(request.Scheme.ToLowerInvariant());
baseStringUri.Append("://");
baseStringUri.Append(request.Host.ToString().ToLowerInvariant());
baseStringUri.Append(request.Path.ToString().ToLowerInvariant());
return baseStringUri.ToString();
}
public static string GetNormalizedParameterString(HttpRequest request)
{
var parameters = new List<(string key,string value)>();
foreach (var queryItem in request.Query)
{
foreach (var queryValue in queryItem.Value)
{
parameters.Add((queryItem.Key,queryValue));
}
}
foreach (var formItem in request.Form)
{
foreach (var formValue in formItem.Value)
{
parameters.Add((formItem.Key,formValue));
}
}
parameters.RemoveAll(_ => _.key == "oauth_signature");
parameters = parameters
.Select(_ => (key: EncodeString(_.key),value: EncodeString(_.value)))
.OrderBy(_ => _.key)
.ThenBy(_ => _.value).ToList();
return string.Join("&",parameters.Select(_ => $"{_.key}={_.value}"));
}
public static string GetSignature(HttpRequest request,string clientSharedSecret,string tokenSharedSecret)
{
string signatureBaseString = GetSignatureBaseString(request);
return GetSignature(signatureBaseString,clientSharedSecret,tokenSharedSecret);
}
public static string GetSignature(string signatureBaseString,string tokenSharedSecret)
{
string key = $"{EncodeString(clientSharedSecret)}&{EncodeString(tokenSharedSecret)}";
var signatureAlgorithm = new HMACSHA1(Encoding.ASCII.GetBytes(key));
byte[] digest = signatureAlgorithm.ComputeHash(Encoding.ASCII.GetBytes(signatureBaseString));
return Convert.ToBase64String(digest);
}
public static string GetSignatureBaseString(HttpRequest request)
{
StringBuilder signatureBaseString = new StringBuilder();
signatureBaseString.Append(request.Method.ToUpperInvariant());
signatureBaseString.Append("&");
signatureBaseString.Append(EncodeString(GetBaseStringUri(request)));
signatureBaseString.Append("&");
signatureBaseString.Append(EncodeString(GetNormalizedParameterString(request)));
return signatureBaseString.ToString();
}
public static bool VerifySignature(HttpRequest request,string tokenSharedSecret)
{
string actualSignature = request.Form["oauth_signature"];
string expectedSignature = GetSignature(request,tokenSharedSecret);
return expectedSignature == actualSignature;
}
private static bool[] CreateUnreservedCharacterMask()
{
bool[] mask = new bool[byte.MaxValue];
// hyphen
mask[45] = true;
// period
mask[46] = true;
// 0-9
for (int pos = 48; pos <= 57; pos++)
{
mask[pos] = true;
}
// A-Z
for (int pos = 65; pos <= 90; pos++)
{
mask[pos] = true;
}
// underscore
mask[95] = true;
// a-z
for (int pos = 97; pos <= 122; pos++)
{
mask[pos] = true;
}
// tilde
mask[126] = true;
return mask;
}
}