如果你在读这本书,那是因为你理解保护你的 web API 的重要性。 ASP.NET Web API 是一个框架,它帮助构建可以被广泛的客户端利用的 HTTP 服务。 所以确保 Web API 的安全是非常重要的。
ASP.NET Web API 1.0 没有任何安全特性,所以安全是由主机(如 Internet Information Server)提供的。 在 ASP.NET Web API 2,安全特性如 Katana 被引入。 为了保护 Web API,让我们了解所涉及的各种技术,并选择正确的方法。
在本章中,我们将涵盖以下主题:
本节将向您概述 Web API 安全体系结构,并向您展示可用于安全相关事务的所有各种扩展点。 ASP.NET Web API 安全体系结构由三个主要层组成。 宿主层充当 Web API 和网络堆栈之间的接口。 消息处理程序管道层支持实现诸如认证和缓存等横切关注点。 控制器处理层是执行控制器和操作、绑定和验证参数以及创建 HTTP 响应消息的地方。 该层还包含一个过滤器管道,如下图所示:
图 1 -此图像显示了保护 Web API 所涉及的组件
让我们简要讨论一下 Web API 管道中每个组件的用途,如下所示:
现在我们已经熟悉了安全体系结构,接下来我们将设置客户机。
设置浏览器客户端让我们创建一个用于联系人查找的 Web API。 这个联系人查找 Web API 服务将向调用的客户端应用返回联系人列表。 然后,我们将使用 jQuery AJAX 调用来列出和搜索联系人,使用 Contact Lookup 服务。
这个应用将帮助我们在本书中演示 Web API 安全性。
在这个节中,我们将创建一个联系人查找 web API 服务,该服务返回一个联系人列表,该列表采用Javascript 对象标记(JSON)格式。 使用这个联系人查找的客户端是一个简单的 web 页面,它使用 jQuery 显示联系人列表。 按照以下步骤启动项目:
Name the project ContactLookup
and click OK, as shown in the following screenshot:
图 2 -我们已经命名了 ASP.NET Web 应用“ContactLookup”
在新 ASP. net 中选择空模板。 对话框。
Check Web API and click OK under Add folders and core references, as shown in the following:
图 3 -我们选择空 Web API 模板
我们只是创建了一个空的 Web API 项目。 现在让我们添加所需的模型。
让我们从开始,通过以下步骤的帮助创建一个代表联系人的简单模型:
First, define a simple contact model by adding a class file to the Models folder.
图 4 -右键单击 Models 文件夹并添加一个 Model 类
命名类文件Contact
并声明Contact
类的属性。
namespace ContactLookup.Models
{
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Mobile { get; set; }
}
}
我们刚刚添加了一个名为Contact
的模型。 现在让我们添加所需的 web API 控制器。
HTTP 请求是由 Web API 中的控制器对象处理的。 让我们定义一个带有两个动作方法的控制器。 一个操作返回联系人列表,另一个操作返回特定于给定 ID 的单个联系人:
Add the Controller under the Controllers folder in Solution Explorer.
图 5 -右键单击 Controllers 文件夹并添加控制器
Select Web API Controller – Empty and click on Add in the Add Scaffold dialog.
图 6 -选择一个空的 Web API 控制器
Let's name the controller ContactsController
in the Add Controller dialog box and click Add.
图 7 -命名控制器
这将在Controllers文件夹中创建ContactsController.cs
文件,如下图所示:
图 8 -应用中的 Controllers 文件夹中添加了 ContactsController
用以下代码替换ContactsController
中的代码:
```
namespace ContactLookup.Controllers
{
public class ContactsController : ApiController
{
Contact[] cOntacts= new Contact[]
{
new Contact { Id = 1, Name = "Steve", Email = "steve@gmail.com", Mobile = "+1(234)35434" },
new Contact { Id = 2, Name = "Matt", Email = "matt@gmail.com", Mobile = "+1(234)5654" },
new Contact { Id = 3, Name = "Mark", Email = "mark@gmail.com", Mobile = "+1(234)56789" }
};
public IEnumerable<Contact> GetAllContacts()
{
return contacts;
}
public IHttpActionResult GetContact(int id)
{
var contact = contacts.FirstOrDefault(x => x.Id == id);
if (contact == null)
{
return NotFound();
}
return Ok(contact);
}
}
}
```
为简单起见,联系人存储在控制器类中的固定数组中。 控制器定义为,有两个动作方法。 联系人列表将由GetAllContacts方法以 JSON 格式返回,GetContact方法根据其 ID 返回单个联系人。 一个惟一的 URI 应用于控制器上的每个方法,如下表所示:
|
控制器方法
|
尤里
|
| --- | --- |
| GetAllContacts | /api/contacts |
| GetContact | /api/contacts/id |
在这个节中,为了演示使用或不使用任何安全机制调用 web API,让我们创建一个 HTML 页面,使用 web API 并使用 jQuery AJAX 调用的结果更新页面:
In the Solution Explorer pane, right-click on the project and add New Item.
图 9 -从解决方案资源管理器的上下文菜单中选择 add new item
Create HTML Page named index.html
using the Add New Item dialog.
图 10 -添加索引 html 文件通过选择 html 页面在添加新项目对话框
用以下代码替换index.html
文件中的内容:
```
var uri = 'api/contacts';
$(document).ready(function () {
// Send an AJAX request
$.getJSON(uri)
.done(function (data) {
// On success, &#39;data&#39; contains a list of contacts.
$.each(data, function (key, contact) {
// Add a list item for the contact.
$(&#39;
});
});
});
function formatItem(contact) {
return contact.Name + ', email: ' + contact.Email + ', mobile: ' + contact.Mobile;
}
function search() {
var id = $(&#39;#contactId&#39;).val();
$.getJSON(uri + &#39;/&#39; + id)
.done(function (data) {
$(&#39;#contact&#39;).text(formatItem(data));
})
.fail(function (jqXHR, textStatus, err) {
$(&#39;#contact&#39;).text(&#39;Error: &#39; + err);
});
}
```
我们需要向/api/contacts
发送一个 HTTP GET 请求来获取联系人列表。 AJAX 请求由 jQuerygetJSON
函数发送,并在响应中接收 JSON 对象数组。 如果请求成功,将调用done
函数中的回调函数。 在回调中,我们用联系信息更新 DOM,如下所示:
$(document).ready(function () {
// Send an AJAX request
$.getJSON(uri)
.done(function (data) {
// On success, 'data' variable contains a list of contacts.
$.each(data, function (key, contact) {
// Add a list item for the contact.
$('
});
});
});
要通过 ID 获取联系人,向/api/contacts/id
发送一个 HTTP get 请求,其中id
是联系人 ID。
function search() {
var id = $('#contactId').val();
$.getJSON(uri + '/' + id)
.done(function (data) {
$('#contact').text(formatItem(data));
})
.fail(function (jqXHR, textStatus, err) {
$('#contact').text('Error: ' + err);
});
}
getJSON
中的请求 URL 有联系人 ID。 响应是此请求的单个联系人的 JSON 表示。
按F5启动调试应用。 输入联系人 ID,点击搜索:
图 11 -样例基于浏览器的客户端应用的用户界面
认证授权我们创建了一个简单的 web API,它根据 ID 返回联系人列表或特定的联系人。 任何支持 HTTP 的客户端都可以访问这个 web API,但安全性还不够。 在认证和授权机制的帮助下,我们可以保护这个 web API 免受未经授权的访问。
认证是在主机Internet Information Service(IIS)中对 web API 进行认证。 Internet 信息服务使用 HTTP 模块进行认证。 我们还可以用自己的 HTTP 模块实现自定义认证。
主机在对用户进行认证时创建主体。 Principal 是一个IPrincipal
对象,它表示运行代码的安全上下文。 您可以从主机连接的Thread.CurrentPrincipal
中访问当前的主体。 可以从 principal 的Identity
对象访问用户信息。 如果用户通过认证,Identity.IsAuthenticated
属性返回true。 如果用户没有被验证,Identity.IsAuthenticated
将返回false。
授权在向控制器提供成功的认证之后发生。 当做出更细粒度的选择时,它帮助您授予对资源的访问权。
对于任何未经授权的请求,授权筛选器将返回一个错误响应,并且不允许执行该操作。 这是因为授权过滤器将在控制器操作中的任何语句之前首先执行。
实现 HTTP 消息处理程序中的认证对于一个自托管 web API,最佳的实践是在HTTP 消息处理程序中实现认证。 主体将在验证 HTTP 请求后由消息处理程序设置。 对于自托管的 web API,可以考虑在消息处理程序中实现认证。 否则,请使用 HTTP 模块。
下面的代码片段展示了一个在 HTTP 模块中实现的基本认证示例:
public class AuthenticationHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var credentials = ParseAuthorizationHeader(request);
if (credentials != null)
{
// Check if the username and passowrd in credentials are valid against the ASP.NET membership.
// If valid, the set the current principal in the request context
var identity = new GenericIdentity(credentials.Username);
Thread.CurrentPrincipal = new GenericPrincipal(identity, null);;
}
return base.SendAsync(request, cancellationToken)
.ContinueWith(task =>
{
var response = task.Result;
if (credentials == null && response.StatusCode == HttpStatusCode.Unauthorized)
Challenge(request, response);
return response;
});
}
protected virtual Credentials ParseAuthorizationHeader(HttpRequestMessage request)
{
string authorizationHeader = null;
var authorization = request.Headers.Authorization;
if (authorization != null && authorization.Scheme == "Basic")
authorizationHeader = authorization.Parameter;
if (string.IsNullOrEmpty(authorizationHeader))
return null;
authorizationHeader = Encoding.Default.GetString(Convert.FromBase64String(authorizationHeader));
var authenticationTokens = authorizationHeader.Split(':');
if (authenticationTokens.Length < 2)
return null;
return new Credentials() { Username = authenticationTokens[0], Password = authenticationTokens[1], };
}
void Challenge(HttpRequestMessage request, HttpResponseMessage response)
{
response.Headers.Add("WWW-Authenticate", string.Format("Basic realm=\"{0}\"", request.RequestUri.DnsSafeHost));
}
public class Credentials
{
public string Username { get; set; }
public string Password { get; set; }
}
}
如果应用实现了自定义认证逻辑,那么我们必须在两个地方设置主体:
Thread.CurrentPrincipal
是。net 中设置线程主体的标准方法。HttpContext.Current.User
是特定于 ASP.NET 的。下面的代码显示了如何设置主体:
private void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
AuthorizeAttribute
将确保用户是通过验证还是未通过验证。 如果用户未通过认证,且不调用相应的操作,则返回 HTTP 状态码为 401 的未授权错误。 Web API 允许您以三种方式应用筛选器。 我们可以在全局级别、控制器级别或单个操作级别应用它们。
为了对所有 Web API 控制器应用授权过滤器,我们需要将AuthorizeAttribute
过滤器添加到Global.asax
文件的全局过滤器列表中,如下所示:
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new AuthorizeAttribute());
}
要为特定的控制器应用授权过滤器,我们需要用 filter 属性装饰控制器,如下代码所示:
// Require authorization for all actions on the controller.
[Authorize]
public class ContactsController : ApiController
{
public IEnumerable<Contact> GetAllContacts() { ... }
public IHttpActionResult GetContact(int id) { ... }
}
要对特定的动作应用授权过滤器,我们需要将属性添加到 action 方法中,如下代码所示:
public class ContactsController : ApiController
{
public IEnumerable<Contact> GetAllContacts() { ... }
// Require authorization for a specific action.
[Authorize]
public IHttpActionResult GetContact(int id) { ... }
}
要实现自定义授权筛选器,我们需要创建派生AuthorizeAttribute
、AuthorizationFilterAttribute
或IAuthorizationFilter
的类。
AuthorizeAttribute
:根据当前用户和用户角色进行授权。AuthorizationFilterAttribute
:应用同步授权逻辑,且可能不基于当前用户或角色。IAuthorizationFilter
:AuthorizeAttribute
和AuthorizationFilterAttribute
都实现IAuthorizationFilter
。 如果需要高级授权逻辑,则需要实现IAuthorizationFilter
。有时,可能需要在基于主体处理请求后更改行为。 在这种情况下,我们可以在控制器操作中实现授权。 例如,如果您想根据用户的角色操作响应,我们可以通过动作方法本身的ApiController.User
属性验证登录的用户角色:
public HttpResponseMessage Get()
{
if (!User.IsInRole("Admin"))
{
// manipulate the response to eliminate information that shouldn't be shared with non admin users
}
}
这很容易,不是吗? 我们刚为 APS 设置了安全系统.NET Web API,我们将在接下来的章节中构建它。
您学习了 ASP 的安全体系结构.NET Web API,它提供了一个整体的视图。 然后我们设置浏览器客户机,从实现 Web 查找服务到使用 Javascript 和 jQuery 代码调用 Web API。
您还学习了认证和授权技术,我们将在本书后面详细介绍这些技术。 接下来,您了解了 HTTP 消息处理程序、Principal 和用于控制用户授权的[Authorize]属性。
最后,您了解了自定义授权和控制器操作中的授权,以便在基于主体处理请求后更改行为。
你在这一章学到了很多东西。 然而,这仅仅是个开始。 在下一章中,您将实现 Web API 的安全套接字层。 让我们开始吧!