我有一个带MVC的Web API应用程序。当用户使用该网站时,身份验证和授权当前由我使用的全局表单身份验证自动处理,在Web.config中配置如下:
<authentication mode="Forms">
<forms loginUrl="~/Login" slidingExpiration="true" timeout="1800" defaultUrl="/"></forms>
</authentication>
<authorization>
<deny users="?" />
</authorization>
这确保只有登录的用户才能访问该站点并调用API。
但我还有一个外部Windows客户端,我想使用另一种身份验证方法。在没有表单身份验证的测试中,我设置了一个自定义的AuthorizeAttribute,我可以在我的控制器中使用这样的:
[ApiAuth]
public IEnumerable<string> Get() {
// Return the resource
}
AuthorizeAttribute看起来像这样:
public class ApiAuthAttribute : AuthorizeAttribute {
public override void OnAuthorization(HttpActionContext context) {
// Authenticate the request with a HMAC-based approach
}
}
这在隔离方面工作正常但我无法弄清楚如何允许两种auth方法。我希望ApiAuth作为后备,如果表单auth不起作用(或反过来,无论什么工作),但如果我应用[ApiAuth]属性,只会使用它,普通用户无法访问api。 / p>
那么,我如何使用多个auth方法,或者使用其中一个作为后备,如果另一个失败,或者配置服务器,以便Windows客户端可以以其他方式调用API,然后仍然使用MVC应用程序,保持两种类型的客户端都可以使用相同的API调用吗?
谢谢。
编辑:我可能采取的一种方法是让Windows客户端使用表单身份验证(类似this)进行身份验证,但它看起来非常像黑客和我宁愿使用其他方法。
答案 0 :(得分:1)
当我在下面引用令牌时,请注意我使用以下组合引用哈希:
例如。您可以将用户名/ hh:mm:ss:ms /完全限定路径/ enpoint / enpoint参数哈希到用户的令牌中。然后你必须决定令牌在滑动到期时是否有效,30分钟,还是仅对每个请求有效。
我会为您的测试应用程序添加一个匿名端点以进行身份验证。此端点应接受用户凭据并返回与Ticket表中的条目匹配的标记,该条目表示具有到期的用户。基本上,由于您没有为每个请求附加票证,因此您必须以某种方式自行管理,因为我建议使用http authorization header
。
public ActionResult GetAuthententicationToken(Credentials credentials)
{
//Authenticate the user
//Insert a record into the Ticket database table and return hash key as token.
//Return the token to the client.
}
现在,客户端(您的测试应用程序)已根据现有凭据进行身份验证,并具有表示该握手的令牌。
您的测试应用现在只需使用get GetAuthententicationToken().
现在,您可以实施AuthorizeAttribute
,在这种情况下,您希望使用先前存储的内容以及对匿名GetAuthententicationToken
方法的成功调用来验证授权标头令牌。
public class ApiAuthAttribute : AuthorizeAttribute {
public override void OnAuthorization(HttpActionContext context) {
//Get authorization token from header
//if caching then get associated Ticket from cache else lookup in database
//if not valid throw security exception
//Apply principal to current user based on lookup above
}
}
那么如何处理FormsAuthentication
并考虑上述方案?
由于表单身份验证在请求处理中早于MVC授权处理,因此当您通过表单方法对用户进行身份验证时,您有一个完美的机会将自定义授权标头添加到传入请求中。
在您对表单身份验证进行身份验证的同一位置添加类似于下面的内容。
public FormsAthentication.CreateAuthenticationTicket()
{
//Authenticate user
//Insert a record into the Ticket database table and return hash key as token.
//Add that token to ticket's data
}
接下来,您需要确保每个请求都应用自定义授权标头。执行此操作的最佳位置是Global.asax文件中的Application_AuthenticateRequest
。
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
//if FormsAuthentication.IsAuthenticated
//Get the token saved in the ticket data
//Save the token value in the http authorization header
}
注意:上面提到的故障单数据库表应该保存一个有效的身份验证请求,其中包含截止日期的日期时间戳。您必须确保拥有一个在后台运行的进程,以通过删除过期的会话记录来强制执行超时。
答案 1 :(得分:1)
FormAuthentication可以实现多种方式。在过去,我们使用FormAuthentication Ticket。
现在,您可以使用基于声明的身份验证与Owin Middleware,它基本上是ASP.Net Identity的精简版本。
在 ApiAuthAttribute 中对用户进行身份验证后,您将创建Principal对象。
您不应在ASP.Net MVC中使用<authorization>
标记。相反,您想使用 过滤器 。
<authentication mode="Forms">
<forms loginUrl="~/Account/Login" timeout="2880" />
</authentication>
public class ApiAuthAttribute : AuthorizeAttribute
{
public override void OnAuthorization(HttpActionContext context)
{
// Authenticate the request with a HMAC-based approach
// Create FormAuthentication after custom authentication is successful
if (!HttpContext.Current.User.Identity.IsAuthenticated)
{
User user = new User {Id = "1234", UserName = "johndoe",
FirstName = "John", LastName = "Doe"};
// This should be injected using IoC container.
var service = new OwinAuthenticationService(
new HttpContextWrapper(HttpContext.Current));
service.SignIn(user);
}
}
}
public class User
{
public string Id { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public interface IAuthenticationService
{
void SignIn(User user);
void SignOut();
}
public class OwinAuthenticationService : IAuthenticationService
{
private readonly HttpContextBase _context;
private const string AuthenticationType = "ApplicationCookie";
public OwinAuthenticationService(HttpContextBase context)
{
_context = context;
}
public void SignIn(User user)
{
IList<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Sid, user.Id),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.GivenName, user.FirstName),
new Claim(ClaimTypes.Surname, user.LastName),
};
/*foreach (Role role in user.Roles)
{
claims.Add(new Claim(ClaimTypes.Role, role.Name));
}*/
ClaimsIdentity identity = new ClaimsIdentity(claims, AuthenticationType);
IOwinContext context = _context.Request.GetOwinContext();
IAuthenticationManager authenticationManager = context.Authentication;
authenticationManager.SignIn(identity);
}
public void SignOut()
{
IOwinContext context = _context.Request.GetOwinContext();
IAuthenticationManager authenticationManager = context.Authentication;
authenticationManager.SignOut(AuthenticationType);
}
}
[assembly: OwinStartup(typeof(YOUR_APPLICATION.Startup))]
namespace YOUR_APPLICATION
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ApplicationCookie",
LoginPath = new PathString("/Account/Login")
});
}
}
}