(a) 仅向已认证用户提供服务。至于身份验证,我想使用简单成员身份验证,因为它是MVC的最新身份验证技术,可以让我定义自己的数据库表,提供开箱即用的OAuth支持,并且很容易与MVC和WebApi集成。
(b) 通过WebApi公开一些核心功能,供移动/JS客户端使用,这些客户端应该通过基本HTTP身份验证(+SSL)进行身份验证。通常情况下,我会有使用jQuery AJAX调用带有不同用户角色Authorize属性的WebApi控制器的JS客户端。
(c) 理想情况下,在混合环境中,我希望避免双重身份验证:即如果用户已经通过浏览器进行了身份验证,并且正在访问涉及JS调用WebApi控制器操作的页面,则(a)机制应该足够。
因此,虽然(a)由默认MVC模板处理,但(b)需要基本HTTP身份验证而无需浏览器介入。为此,我应该创建一个DelegatingHandler,就像我在这篇文章中找到的那样:http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers。问题在于,它的实现需要一种从接收到的用户名和密码中检索IPrincipal的方法,而WebSecurity类没有提供任何此类方法(除了Login,但我会避免仅为授权目的更改已登录用户,也因为存在潜在的“混合”环境,如(c)所述)。因此,似乎我的唯一选择是放弃简单成员身份验证。有没有更好的建议?这里是引用自上述文章的相关(略作修改)代码:
public interface IPrincipalProvider
{
IPrincipal GetPrincipal(string username, string password);
}
public sealed class Credentials
{
public string Username { get; set; }
public string Password { get; set; }
}
public class BasicAuthMessageHandler : DelegatingHandler
{
private const string BasicAuthResponseHeader = "WWW-Authenticate";
private const string BasicAuthResponseHeaderValue = "Basic";
public IPrincipalProvider PrincipalProvider { get; private set; }
public BasicAuthMessageHandler(IPrincipalProvider provider)
{
if (provider == null) throw new ArgumentNullException("provider");
PrincipalProvider = provider;
}
private static Credentials ParseAuthorizationHeader(string sHeader)
{
string[] credentials = Encoding.ASCII.GetString(
Convert.FromBase64String(sHeader)).Split(new[] { ':' });
if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) ||
String.IsNullOrEmpty(credentials[1])) return null;
return new Credentials
{
Username = credentials[0],
Password = credentials[1],
};
}
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
AuthenticationHeaderValue authValue = request.Headers.Authorization;
if (authValue != null && !String.IsNullOrWhiteSpace(authValue.Parameter))
{
Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
if (parsedCredentials != null)
{
Thread.CurrentPrincipal = PrincipalProvider
.GetPrincipal(parsedCredentials.Username, parsedCredentials.Password);
}
}
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
var response = task.Result;
if (response.StatusCode == HttpStatusCode.Unauthorized
&& !response.Headers.Contains(BasicAuthResponseHeader))
{
response.Headers.Add(BasicAuthResponseHeader,
BasicAuthResponseHeaderValue);
}
return response;
});
}
}