作者:里克·安德森
你已经创建了一个 Web API,但现在想要控制对它的访问。 在此系列文章中,我们将介绍一些用于保护未经授权的用户的 Web API 的选项。 本系列将涵盖身份验证和授权。
- 身份验证 是了解用户的身份。 例如,Alice 使用用户名和密码登录,服务器使用密码对 Alice 进行身份验证。
- 授权 决定是否允许用户执行操作。 例如,Alice 有权获取资源,但无权创建资源。
本系列中的第一篇文章概述了 ASP.NET Web API 中的身份验证和授权。 其他主题介绍 Web API 的常见身份验证方案。
注释
感谢审阅本系列并提供宝贵反馈的人:里克·安德森、利维·布罗德里克、巴里·多兰斯、汤姆·迪克斯特拉、洪梅·葛、戴维德·马特森、丹尼尔·罗斯、蒂姆·泰布肯。
身份验证
Web API 假定身份验证发生在主机中。 对于 Web 托管,主机是 IIS,它使用 HTTP 模块进行身份验证。 可以将项目配置为使用任何内置到 IIS 或 ASP.NET 的身份验证模块,或编写自己的 HTTP 模块来执行自定义身份验证。
当主机对用户进行身份验证时,它会创建一个主体,该 主体是一个 IPrincipal 对象,该对象表示运行代码的安全上下文。 主机通过设置 Thread.CurrentPrincipal 将主体附加到当前线程。 主体包含一个关联的 Identity 对象,该对象包含有关用户的信息。 如果用户进行身份验证, Identity.IsAuthenticated 属性将返回 true。 对于匿名请求, IsAuthenticated 返回 false。 有关主体的详细信息,请参阅基于角色的安全性。
用于身份验证的 HTTP 消息处理程序
可以将身份验证逻辑放入 HTTP 消息处理程序中,而不是使用主机进行身份验证。 在这种情况下,消息处理程序会检查 HTTP 请求并设置主体。
何时应使用消息处理程序进行身份验证? 下面是一些权衡:
- HTTP 模块将看到所有通过 ASP.NET 管道的请求。 消息处理程序仅看到路由到 Web API 的请求。
- 可以设置每路由消息处理程序,以便将身份验证方案应用到特定路由。
- HTTP 模块特定于 IIS。 消息处理程序与主机无关,因此它们可用于 Web 托管和自承载。
- HTTP 模块参与 IIS 日志记录、审核等。
- HTTP 模块在管道中较早运行。 如果在消息处理程序中处理身份验证,则在处理程序运行之前,不会设置主体。 此外,当响应离开消息处理程序时,主体将还原回以前的主体。
通常,如果不需要支持自承载,则 HTTP 模块是更好的选择。 如果需要支持自托管,请考虑消息处理程序。
设置主体
如果应用程序执行任何自定义身份验证逻辑,则必须在两个位置设置主体:
- Thread.CurrentPrincipal。 此属性是在 .NET 中设置线程主体的标准方法。
- HttpContext.Current.User。 此属性特定于 ASP.NET。
以下代码演示如何设置主体:
private void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
对于 Web 托管,必须在两个位置设置主体;否则,安全上下文可能会不一致。 但是,对于自承载, HttpContext.Current 为 null。 若要确保代码的宿主无关性,请在分配给 HttpContext.Current 之前检查是否为 null,如下所示。
授权
授权是在后面的管道中进行,更接近控制器。 这样,便可以在授予对资源的访问权限时做出更精细的选择。
- 授权筛选器 在控制器操作之前运行。 如果请求未获得授权,筛选器将返回错误响应,并且不会调用该操作。
- 在控制器操作中,可以从 ApiController.User 属性获取当前主体。 例如,可以基于用户名筛选资源列表,只返回属于该用户的那些资源。
使用 [Authorize] 属性
Web API 提供内置的授权筛选器 AuthorizeAttribute。 此筛选器检查用户是否已进行身份验证。 如果没有,它将返回 HTTP 状态代码 401(未授权),而无需调用该操作。
可以在全局、控制器级别或单个操作级别应用筛选器。
全局:若要限制每个 Web API 控制器的访问,请将 AuthorizeAttribute 筛选器添加到全局筛选器列表:
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new AuthorizeAttribute());
}
控制器:若要限制特定控制器的访问,请将筛选器作为属性添加到控制器:
// Require authorization for all actions on the controller.
[Authorize]
public class ValuesController : ApiController
{
public HttpResponseMessage Get(int id) { ... }
public HttpResponseMessage Post() { ... }
}
操作:若要限制特定操作的访问,请将属性添加到操作方法:
public class ValuesController : ApiController
{
public HttpResponseMessage Get() { ... }
// Require authorization for a specific action.
[Authorize]
public HttpResponseMessage Post() { ... }
}
或者,可以使用属性 [AllowAnonymous] 来限制控制器,然后允许匿名访问特定操作。 在以下示例中, Post 该方法受到限制,但 Get 该方法允许匿名访问。
[Authorize]
public class ValuesController : ApiController
{
[AllowAnonymous]
public HttpResponseMessage Get() { ... }
public HttpResponseMessage Post() { ... }
}
在前面的示例中,筛选器允许任何已认证的用户访问受限方法,仅匿名用户被排除在外。你也可以限制特定用户或特定角色用户的访问权限:
// Restrict by user:
[Authorize(Users="Alice,Bob")]
public class ValuesController : ApiController
{
}
// Restrict by role:
[Authorize(Roles="Administrators")]
public class ValuesController : ApiController
{
}
注释
Web API 控制器的 AuthorizeAttribute 筛选器位于 System.Web.Http 命名空间中。 System.Web.Mvc 命名空间中的 MVC 控制器有类似的筛选器,它与 Web API 控制器不兼容。
自定义授权筛选器
若要编写自定义授权筛选器,请派生自以下类型之一:
- AuthorizeAttribute。 扩展此类以基于当前用户和用户的角色执行授权逻辑。
- AuthorizationFilterAttribute。 扩展此类以执行不一定基于当前用户或角色的同步授权逻辑。
- IAuthorizationFilter。 实现此接口以执行异步授权逻辑;例如,如果授权逻辑进行异步 I/O 或网络调用。 (如果授权逻辑是 CPU 绑定的,则从 AuthorizationFilterAttribute 派生起来更简单,因为这样就不需要编写异步方法。
下图显示了 AuthorizeAttribute 类的类层次结构。
Authorize Attribute 类的类层次结构关系图。 授权属性位于底部,箭头指向授权筛选器属性,箭头指向顶部的 I Authorization Filter。
控制器操作中的授权
在某些情况下,可以允许请求继续,但会根据主体更改行为。 例如,返回的信息可能会根据用户的角色而更改。 在控制器方法中,可以从 ApiController.User 属性获取当前主体。
public HttpResponseMessage Get()
{
if (User.IsInRole("Administrators"))
{
// ...
}
}