为 Azure App Service WebApi OWIN 启用基本身份验证
Enable Basic Authentication for Azure App Service WebApi OWIN
我有一个 ASP.Net 4.5.2 MVC5 WebApi 5.2.3 应用程序,它通常通过 OWIN 管道使用 OAuth/Jwt 安全性。现在,我有一个外部服务 web-hook,它通过 POST 调用我的一个 WebApi 控制器。此 webhook 服务仅支持基于 HTTPS 的基本身份验证。这很好,如果我只能让我的过滤器(或其他)工作。
我的过滤器基于一个很好的 example by Rick Strahl 但是,当我添加这个 class 然后我的 [MyBasicAuthFilter] 在我的 ApiController 操作之上时,我得到的只是一条服务器错误 500 消息,其中包含 { "Message":"An error has occurred."}。
当我从 POST 调用中删除 "Authorization: Basic dXNlcasdfasfdsfasd=" header 时,代码会正常进入我的新过滤器。哦,太好了 :-[。因此,管道中较早的部分不喜欢这个 header 存在的事实。阅读了一下后,我怀疑 IIS 设置。但是在尝试通过我的 Web.Config 强制 Azure App Service IIS 并迅速阻止对我网站的所有访问之后,我想我应该来这里寻求一些提示。
Ideas/Tips?
好的,这是关于这里发生的事情的独家新闻。现有的 JsonWebTokenValidationHandler(消息处理程序)仅在 header 中寻找 "Bearer" 令牌。如果出现任何其他令牌,那么它会将其标记为 return HttpStatus 500 的潜在攻击。如果 header 中根本没有令牌,那么它将 return 挑战 HttpStatus 401 . 无论如何,我的简约 BasicAuth 要求(在 JWT 令牌之上)的最佳解决方案是我 JsonWebTokenValidationHandler.cs 的以下(完整)内容。这现在可以处理 Bearer (JWT) 令牌和 Basic Auth 令牌。欢迎评论。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
public class JsonWebTokenValidationHandler : DelegatingHandler
{
public string SymmetricKey { get; set; } // A0 client secret
public string Audience { get; set; } // A0 client ID
public string Issuer { get; set; } // A0 our A0 domain
private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
{
token = null;
IEnumerable<string> authzHeaders;
if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1)
{
// Fail if no Authorization header or more than one Authorization headers
// are found in the HTTP request
return false;
}
// Remove the bearer token scheme prefix and return the rest as ACS token
var bearerToken = authzHeaders.ElementAt(0);
token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
return true;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string token;
HttpResponseMessage errorResponse = null;
if (TryRetrieveToken(request, out token))
{
try
{
// ++
// Added BasicAuth to our JWT Auth
// If it is a BasicAuth token, then check it first and bypass JWT validation
// See Asp.Net WebApi security book for infos on this.
//
if (token.Contains("Basic"))
{
var authHeader = Encoding.Default.GetString(Convert.FromBase64String(token.Substring(6)));
// find first : as password allows for :
int idx = authHeader.IndexOf(':');
if (idx < 0)
return null;
string username = authHeader.Substring(0, idx);
string password = authHeader.Substring(idx + 1);
// Check the user's validity and reuse the JWT challenge 401 exception if not valid.
if (!ValidateBasicAuthUser(username, password))
{
throw new JsonWebToken.TokenValidationException(
$"Username or password missmatch for username: {username}");
}
// If OK, then let them in and continue
var identity = new BasicAuthenticationIdentity(username, password);
var principal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = Thread.CurrentPrincipal;
}
}
else
{
// --
// Handle JWT Tokens here or return server error 500 if it is not a valid token.
var secret = this.SymmetricKey.Replace('-', '+').Replace('_', '/');
Thread.CurrentPrincipal = JsonWebToken.ValidateToken(
token,
secret,
audience: this.Audience,
checkExpiration: true,
issuer: this.Issuer);
if (HttpContext.Current != null)
{
HttpContext.Current.User = Thread.CurrentPrincipal;
}
}
}
catch (JWT.SignatureVerificationException ex)
{
errorResponse = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex);
}
catch (JsonWebToken.TokenValidationException ex)
{
errorResponse = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex);
}
catch (Exception ex)
{
errorResponse = request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
}
}
return errorResponse != null ?
Task.FromResult(errorResponse) :
base.SendAsync(request, cancellationToken);
}
/// <summary>
/// rht: Check our BasicAuth passwords e.g. from Cb WebHook
/// Usage: http://weblog.west-wind.com/posts/2013/Apr/18/A-WebAPI-Basic-Authentication-Authorization-Filter
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
private bool ValidateBasicAuthUser(string username, string password)
{
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
return false;
var ok = false;
//
// Our valid usernames and passwords
//
switch (username)
{
case "your user 1 here":
{
if (password == "your password here") ok = true;
}
break;
case "your user 2 here or from DB, whatever":
{
if (password == "pwd") ok = true;
}
break;
}
return ok;
}
} // cl
//
// See: http://weblog.west-wind.com/posts/2013/Apr/18/A-WebAPI-Basic-Authentication-Authorization-Filter
// And Asp.Net WebApi Security book.
public class BasicAuthenticationIdentity : GenericIdentity
{
public BasicAuthenticationIdentity(string name, string password)
: base(name, "Basic")
{
this.Password = password;
}
/// <summary>
/// Basic Auth Password for custom authentication
/// </summary>
public string Password { get; set; }
}
我有一个 ASP.Net 4.5.2 MVC5 WebApi 5.2.3 应用程序,它通常通过 OWIN 管道使用 OAuth/Jwt 安全性。现在,我有一个外部服务 web-hook,它通过 POST 调用我的一个 WebApi 控制器。此 webhook 服务仅支持基于 HTTPS 的基本身份验证。这很好,如果我只能让我的过滤器(或其他)工作。
我的过滤器基于一个很好的 example by Rick Strahl 但是,当我添加这个 class 然后我的 [MyBasicAuthFilter] 在我的 ApiController 操作之上时,我得到的只是一条服务器错误 500 消息,其中包含 { "Message":"An error has occurred."}。
当我从 POST 调用中删除 "Authorization: Basic dXNlcasdfasfdsfasd=" header 时,代码会正常进入我的新过滤器。哦,太好了 :-[。因此,管道中较早的部分不喜欢这个 header 存在的事实。阅读了一下后,我怀疑 IIS 设置。但是在尝试通过我的 Web.Config 强制 Azure App Service IIS 并迅速阻止对我网站的所有访问之后,我想我应该来这里寻求一些提示。
Ideas/Tips?
好的,这是关于这里发生的事情的独家新闻。现有的 JsonWebTokenValidationHandler(消息处理程序)仅在 header 中寻找 "Bearer" 令牌。如果出现任何其他令牌,那么它会将其标记为 return HttpStatus 500 的潜在攻击。如果 header 中根本没有令牌,那么它将 return 挑战 HttpStatus 401 . 无论如何,我的简约 BasicAuth 要求(在 JWT 令牌之上)的最佳解决方案是我 JsonWebTokenValidationHandler.cs 的以下(完整)内容。这现在可以处理 Bearer (JWT) 令牌和 Basic Auth 令牌。欢迎评论。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
public class JsonWebTokenValidationHandler : DelegatingHandler
{
public string SymmetricKey { get; set; } // A0 client secret
public string Audience { get; set; } // A0 client ID
public string Issuer { get; set; } // A0 our A0 domain
private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
{
token = null;
IEnumerable<string> authzHeaders;
if (!request.Headers.TryGetValues("Authorization", out authzHeaders) || authzHeaders.Count() > 1)
{
// Fail if no Authorization header or more than one Authorization headers
// are found in the HTTP request
return false;
}
// Remove the bearer token scheme prefix and return the rest as ACS token
var bearerToken = authzHeaders.ElementAt(0);
token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
return true;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string token;
HttpResponseMessage errorResponse = null;
if (TryRetrieveToken(request, out token))
{
try
{
// ++
// Added BasicAuth to our JWT Auth
// If it is a BasicAuth token, then check it first and bypass JWT validation
// See Asp.Net WebApi security book for infos on this.
//
if (token.Contains("Basic"))
{
var authHeader = Encoding.Default.GetString(Convert.FromBase64String(token.Substring(6)));
// find first : as password allows for :
int idx = authHeader.IndexOf(':');
if (idx < 0)
return null;
string username = authHeader.Substring(0, idx);
string password = authHeader.Substring(idx + 1);
// Check the user's validity and reuse the JWT challenge 401 exception if not valid.
if (!ValidateBasicAuthUser(username, password))
{
throw new JsonWebToken.TokenValidationException(
$"Username or password missmatch for username: {username}");
}
// If OK, then let them in and continue
var identity = new BasicAuthenticationIdentity(username, password);
var principal = new GenericPrincipal(identity, null);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = Thread.CurrentPrincipal;
}
}
else
{
// --
// Handle JWT Tokens here or return server error 500 if it is not a valid token.
var secret = this.SymmetricKey.Replace('-', '+').Replace('_', '/');
Thread.CurrentPrincipal = JsonWebToken.ValidateToken(
token,
secret,
audience: this.Audience,
checkExpiration: true,
issuer: this.Issuer);
if (HttpContext.Current != null)
{
HttpContext.Current.User = Thread.CurrentPrincipal;
}
}
}
catch (JWT.SignatureVerificationException ex)
{
errorResponse = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex);
}
catch (JsonWebToken.TokenValidationException ex)
{
errorResponse = request.CreateErrorResponse(HttpStatusCode.Unauthorized, ex);
}
catch (Exception ex)
{
errorResponse = request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex);
}
}
return errorResponse != null ?
Task.FromResult(errorResponse) :
base.SendAsync(request, cancellationToken);
}
/// <summary>
/// rht: Check our BasicAuth passwords e.g. from Cb WebHook
/// Usage: http://weblog.west-wind.com/posts/2013/Apr/18/A-WebAPI-Basic-Authentication-Authorization-Filter
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
private bool ValidateBasicAuthUser(string username, string password)
{
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
return false;
var ok = false;
//
// Our valid usernames and passwords
//
switch (username)
{
case "your user 1 here":
{
if (password == "your password here") ok = true;
}
break;
case "your user 2 here or from DB, whatever":
{
if (password == "pwd") ok = true;
}
break;
}
return ok;
}
} // cl
//
// See: http://weblog.west-wind.com/posts/2013/Apr/18/A-WebAPI-Basic-Authentication-Authorization-Filter
// And Asp.Net WebApi Security book.
public class BasicAuthenticationIdentity : GenericIdentity
{
public BasicAuthenticationIdentity(string name, string password)
: base(name, "Basic")
{
this.Password = password;
}
/// <summary>
/// Basic Auth Password for custom authentication
/// </summary>
public string Password { get; set; }
}