Как защитить веб-API ASP.NET

Я хочу создать веб-службу RESTful с использованием ASP.NET Web API, которые сторонние разработчики будут использовать для доступа к данным моего приложения.

Я довольно много читал об OAuth, и, похоже, это стандарт, но найти хороший пример с документацией, объясняющей, как это работает (и это действительно работает!) Кажется невероятно трудным (особенно для новичка OAuth).

Есть ли образец, который действительно строит и работает, и показывает, как это реализовать?

Я загрузил многочисленные образцы:

  • DotNetOAuth – документация безнадежна с точки зрения новичка
  • Thinktecture – не может заставить его строить

Я также рассматривал блоги, предлагающие простую схему на основе токенов (вроде этого ) – это похоже на повторное изобретательство колеса, но у него есть преимущество в том, что он концептуально довольно прост.

Кажется, есть много вопросов, подобных этому на SO, но нет хороших ответов.

Что все делают в этом пространстве?

Обновить:

Я добавил еще один ответ, как использовать JWT-аутентификацию для веб-API здесь для всех, кто интересуется JWT:

Аутентификация JWT для Asp.Net Web Api


Нам удалось применить аутентификацию HMAC для защиты веб-API, и все получилось хорошо. Аутентификация HMAC использует секретный ключ для каждого потребителя, который как потребитель, так и сервер, как известно, hmac hash сообщение, HMAC256 следует использовать. В большинстве случаев хешированный пароль потребителя используется в качестве секретного ключа.

Обычно сообщение создается из данных в HTTP-запросе или даже настраиваемых данных, которые добавляются в HTTP-заголовок, это сообщение может включать:

  1. Временная метка: время отправки запроса (UTC или GMT)
  2. HTTP-глагол: GET, POST, PUT, DELETE.
  3. сообщения и строку запроса,
  4. URL

Под капотом аутентификация HMAC будет:

Потребитель отправляет HTTP-запрос на веб-сервер после создания подписи (вывод hashа hmac), шаблон HTTP-запроса:

User-Agent: {agent} Host: {host} Timestamp: {timestamp} Authentication: {username}:{signature} 

Пример запроса GET:

 GET /webapi.hmac/api/values User-Agent: Fiddler Host: localhost Timestamp: Thursday, August 02, 2012 3:30:32 PM Authentication: cuongle:LohrhqqoDy6PhLrHAXi7dUVACyJZilQtlDzNbLqzXlw= 

Сообщение для hashа для получения подписи:

 GET\n Thursday, August 02, 2012 3:30:32 PM\n /webapi.hmac/api/values\n 

Пример запроса POST с строкой запроса (подпись ниже неверна, просто пример)

 POST /webapi.hmac/api/values?key2=value2 User-Agent: Fiddler Host: localhost Content-Type: application/x-www-form-urlencoded Timestamp: Thursday, August 02, 2012 3:30:32 PM Authentication: cuongle:LohrhqqoDy6PhLrHAXi7dUVACyJZilQtlDzNbLqzXlw= key1=value1&key3=value3 

Сообщение для hashа для получения подписи

 GET\n Thursday, August 02, 2012 3:30:32 PM\n /webapi.hmac/api/values\n key1=value1&key2=value2&key3=value3 

Обратите внимание, что данные формы и строка запроса должны быть в порядке, поэтому код на сервере получает строку запроса и данные формы для построения правильного сообщения.

Когда HTTP-запрос поступает на сервер, используется фильтр действия аутентификации для синтаксического анализа запроса для получения информации: HTTP-глагол, метка времени, uri, данные формы и строка запроса, а затем на основе этих данных для создания подписи (использование hmac-hashа) с тайной (хешированный пароль) на сервере.

Секретный ключ получен из базы данных с именем пользователя в запросе.

Затем код сервера сравнивает подпись с запросом с созданной подписью; если он равен, аутентификация передается, в противном случае она не удалась.

Код для создания подписи:

 private static string ComputeHash(string hashedPassword, string message) { var key = Encoding.UTF8.GetBytes(hashedPassword.ToUpper()); string hashString; using (var hmac = new HMACSHA256(key)) { var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message)); hashString = Convert.ToBase64String(hash); } return hashString; } 

Итак, как предотвратить повторную атаку?

Добавьте ограничение для метки времени, например:

 servertime - X minutes|seconds <= timestamp <= servertime + X minutes|seconds 

(servertime: время запроса, поступающего на сервер)

И, кэшируйте подпись запроса в памяти (используйте MemoryCache, нужно держать в пределе времени). Если следующий запрос поступает с той же подписью с предыдущим запросом, он будет отклонен.

Демо-код указан здесь: https://github.com/cuongle/Hmac.WebApi

Сначала я хотел бы начать с самых простых решений – возможно, простая простая HTTP-аутентификация + HTTPS достаточно в вашем сценарии.

Если нет (например, вы не можете использовать https или требуется более сложное управление ключами), вы можете взглянуть на решения на основе HMAC, как это предлагают другие. Хорошим примером такого API будет Amazon S3 ( http://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html )

Я написал сообщение в блоге об аутентификации на основе HMAC в веб-интерфейсе ASP.NET. В нем рассматриваются как служба веб-API, так и клиент веб-API, и код доступен на битбакете. http://www.piotrwalat.net/hmac-authentication-in-asp-net-web-api/

Вот сообщение об основной аутентификации в веб-API: http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-message-handlers/

Помните, что если вы собираетесь предоставить API третьим сторонам, вы, скорее всего, будете отвечать за доставку клиентских библиотек. Базовая аутентификация имеет здесь существенное преимущество, поскольку она поддерживается на большинстве программных платформ из коробки. HMAC, с другой стороны, не является стандартизированным и потребует специальной реализации. Они должны быть относительно простыми, но по-прежнему требуют работы.

PS. Существует также возможность использования сертификатов HTTPS +. http://www.piotrwalat.net/client-certificate-authentication-in-asp-net-web-api-and-windows-store-apps/

Вы пробовали DevDefined.OAuth?

Я использовал его для защиты моего WebApi с 2-ногами OAuth. Я также успешно протестировал его с помощью PHP-клиентов.

Очень легко добавить поддержку OAuth, используя эту библиотеку. Вот как вы можете реализовать поставщика ASP.NET MVC Web API:

1) Получить исходный код DevDefined.OAuth: https://github.com/bittercoder/DevDefined.OAuth – самая новая версия позволяет расширять OAuthContextBuilder .

2) Создайте библиотеку и укажите ее в своем проекте веб-API.

3) Создайте настраиваемый конструктор контекста для поддержки построения контекста из HttpRequestMessage :

 using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net.Http; using System.Web; using DevDefined.OAuth.Framework; public class WebApiOAuthContextBuilder : OAuthContextBuilder { public WebApiOAuthContextBuilder() : base(UriAdjuster) { } public IOAuthContext FromHttpRequest(HttpRequestMessage request) { var context = new OAuthContext { RawUri = this.CleanUri(request.RequestUri), Cookies = this.CollectCookies(request), Headers = ExtractHeaders(request), RequestMethod = request.Method.ToString(), QueryParameters = request.GetQueryNameValuePairs() .ToNameValueCollection(), }; if (request.Content != null) { var contentResult = request.Content.ReadAsByteArrayAsync(); context.RawContent = contentResult.Result; try { // the following line can result in a NullReferenceException var contentType = request.Content.Headers.ContentType.MediaType; context.RawContentType = contentType; if (contentType.ToLower() .Contains("application/x-www-form-urlencoded")) { var stringContentResult = request.Content .ReadAsStringAsync(); context.FormEncodedParameters = HttpUtility.ParseQueryString(stringContentResult.Result); } } catch (NullReferenceException) { } } this.ParseAuthorizationHeader(context.Headers, context); return context; } protected static NameValueCollection ExtractHeaders( HttpRequestMessage request) { var result = new NameValueCollection(); foreach (var header in request.Headers) { var values = header.Value.ToArray(); var value = string.Empty; if (values.Length > 0) { value = values[0]; } result.Add(header.Key, value); } return result; } protected NameValueCollection CollectCookies( HttpRequestMessage request) { IEnumerable values; if (!request.Headers.TryGetValues("Set-Cookie", out values)) { return new NameValueCollection(); } var header = values.FirstOrDefault(); return this.CollectCookiesFromHeaderString(header); } ///  /// Adjust the URI to match the RFC specification (no query string!!). ///  ///  /// The original URI. ///  ///  /// The adjusted URI. ///  private static Uri UriAdjuster(Uri uri) { return new Uri( string.Format( "{0}://{1}{2}{3}", uri.Scheme, uri.Host, uri.IsDefaultPort ? string.Empty : string.Format(":{0}", uri.Port), uri.AbsolutePath)); } } 

4) Используйте этот учебник для создания поставщика OAuth: http://code.google.com/p/devdefined-tools/wiki/OAuthProvider . На последнем шаге (Accessing Protected Resource Example) вы можете использовать этот код в атрибуте AuthorizationFilterAttribute :

 public override void OnAuthorization(HttpActionContext actionContext) { // the only change I made is use the custom context builder from step 3: OAuthContext context = new WebApiOAuthContextBuilder().FromHttpRequest(actionContext.Request); try { provider.AccessProtectedResourceRequest(context); // do nothing here } catch (OAuthException authEx) { // the OAuthException's Report property is of the type "OAuthProblemReport", it's ToString() // implementation is overloaded to return a problem report string as per // the error reporting OAuth extension: http://wiki.oauth.net/ProblemReporting actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized) { RequestMessage = request, ReasonPhrase = authEx.Report.ToString() }; } } 

Я реализовал свой собственный провайдер, поэтому я не тестировал вышеуказанный код (за исключением, конечно, WebApiOAuthContextBuilder который я использую в моем провайдере), но он должен работать нормально.

Web API представил атрибут [Authorize] для обеспечения безопасности. Это можно задать глобально (global.asx)

 public static void Register(HttpConfiguration config) { config.Filters.Add(new AuthorizeAttribute()); } 

Или на controller:

 [Authorize] public class ValuesController : ApiController{ ... 

Конечно, ваш тип проверки подлинности может отличаться, и вы можете выполнить свою собственную проверку подлинности. Когда это произойдет, вы можете найти полезное наследование от атрибута Authorizate и расширить его, чтобы он соответствовал вашим требованиям:

 public class DemoAuthorizeAttribute : AuthorizeAttribute { public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext) { if (Authorize(actionContext)) { return; } HandleUnauthorizedRequest(actionContext); } protected override void HandleUnauthorizedRequest(System.Web.Http.Controllers.HttpActionContext actionContext) { var challengeMessage = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized); challengeMessage.Headers.Add("WWW-Authenticate", "Basic"); throw new HttpResponseException(challengeMessage); } private bool Authorize(System.Web.Http.Controllers.HttpActionContext actionContext) { try { var someCode = (from h in actionContext.Request.Headers where h.Key == "demo" select h.Value.First()).FirstOrDefault(); return someCode == "myCode"; } catch (Exception) { return false; } } } 

И в вашем controllerе:

 [DemoAuthorize] public class ValuesController : ApiController{ 

Вот ссылка на другую пользовательскую реализацию для авторизации WebApi:

http://www.piotrwalat.net/basic-http-authentication-in-asp-net-web-api-using-membership-provider/

Если вы хотите защитить свой API от сервера к серверу (без перенаправления на веб-сайт для двухсторонней аутентификации). Вы можете ознакомиться с протоколом Grant Credentials OAuth2.

https://dev.twitter.com/docs/auth/application-only-auth

Я разработал библиотеку, которая поможет вам легко добавить такую ​​поддержку в ваш WebAPI. Вы можете установить его как пакет NuGet:

https://nuget.org/packages/OAuth2ClientCredentialsGrant/1.0.0.0

Библиотека предназначена для .NET Framework 4.5.

Когда вы добавите пакет в свой проект, он создаст файл readme в корне вашего проекта. Вы можете посмотреть этот файл readme, чтобы узнать, как настроить / использовать этот пакет.

Ура!

в продолжение ответа @ Cuong Le, мой подход к предотвращению повторной атаки будет

// Шифрование времени Unix Time на стороне клиента с использованием общего закрытого ключа (или пароля пользователя)

// Отправлять его как часть заголовка запроса на сервер (WEB API)

// Расшифруйте Unix Time at Server (WEB API) с помощью общего закрытого ключа (или пароля пользователя)

// Проверяем разницу во времени между временем Unix Unix и временем Unix сервера, не должно превышать x sec

// если User ID / Hash Password верны, а дешифрованное UnixTime находится в пределах x секунд времени сервера, то это действительный запрос

  • шифровать данные в SharedPreferences
  • Настройка конечной точки сервера авторизации
  • Ограничение токена обновления API Google API
  • google oauth2 redirect_uri с несколькими параметрами
  • Как использовать API входа в Google с помощью Cordova / Phonegap
  • Служба авторизации OAuth в ядре ASP.NET
  • Получить информацию о пользователе через API Google
  • SSO с CAS или OAuth?
  • Поискать страницу от имени пользователя?
  • Безопасность схем аутентификации REST
  • Проблема CORS при выполнении запроса Ajax для токена доступа oauth2
  • Давайте будем гением компьютера.