Как разрешить доступ к ресурсам ServiceStack с помощью токенов доступа OAuth2 через DotNetOpenAuth?

Я создал сервер авторизации OAuth2, используя DotNetOpenAuth, который работает нормально - я использую поток пароля владельца ресурса и успешно меняю учетные данные пользователя для токена доступа.

Теперь я хочу использовать этот токен доступа для извлечения данных из защищенных конечных точек в API ServiceStack, и я не могу решить, как это сделать. Я изучил поставщиков Facebook, Google и т.д., Включенных в ServiceStack, но не понял, следует ли мне следовать одному шаблону или нет.

То, что я пытаюсь достичь (я думаю!),

  • OAuth клиент (мое приложение) запрашивает владельца ресурса ('Catherine Smith') для учетных данных
  • Клиент отправляет запрос серверу авторизации, получает токен доступа
  • Клиент запрашивает безопасный ресурс на сервере (GET /users/csmith/photos)
    • токен доступа включен в HTTP-заголовок, например. Authorization: Bearer 1234abcd...
  • Сервер ресурсов расшифровывает токен доступа , чтобы проверить личность владельца ресурса
  • сервер ресурсов проверяет, что владелец ресурса имеет доступ к запрошенному ресурсу
  • Сервер ресурсов возвращает ресурс для клиента

Шаги 1 и 2 работают, но я не могу понять, как интегрировать код сервера ресурса DotNetOpenAuth с картой авторизации ServiceStack.

Есть ли какой-нибудь пример того, как я мог бы это достичь? Я нашел аналогичный пост StackOverflow в Как создать защищенный api с использованием ServiceStack в качестве сервера ресурсов с OAuth2.0?, но это не полное решение и не похоже, используют модель поставщика авторизации ServiceStack.

РЕДАКТИРОВАТЬ: Немного подробней. Здесь есть два разных веб-приложения. Один из них - сервер аутентификации/авторизации - он не содержит никаких данных клиента (т.е. Никакого API данных), но предоставляет метод /oauth/token, который будет принимать имя пользователя/пароль и возвращать токен доступа OAuth2 и обновлять токен, а также обеспечивает возможность обновления токена. Это построено на ASP.NET MVC, потому что оно почти идентично образцу AuthorizationServer, включенному в DotNetOpenAuth. Это может быть заменено позже, но на данный момент это ASP.NET MVC.

Для фактического API данных я использую ServiceStack, потому что я нахожу его намного лучше, чем WebAPI или MVC для отображения служб данных ReSTful.

Итак, в следующем примере:

sequence diagram

Клиент - настольное приложение, работающее на локальном компьютере пользователя, Auth server - это ASP.NET MVC + DotNetOpenAuth и Сервер ресурсов - ServiceStack

Определенный фрагмент кода DotNetOpenAuth, который требуется:

// scopes is the specific OAuth2 scope associated with the current API call.
var scopes = new string[] { "some_scope", "some_other_scope" }

var analyzer = new StandardAccessTokenAnalyzer(authServerPublicKey, resourceServerPrivateKey);
var resourceServer = new DotNetOpenAuth.OAuth2.ResourceServer(analyzer);
var wrappedRequest = System.Web.HttpRequestWrapper(HttpContext.Current.Request);
var principal = resourceServer.GetPrincipal(wrappedRequest, scopes);

if (principal != null) {
    // We've verified that the OAuth2 access token grants this principal
    // access to the requested scope.
}

Итак, если я нахожусь на правильном пути, мне нужно выполнить этот код где-то в конвейере запроса ServiceStack, чтобы убедиться, что заголовок авторизации в запросе API представляет действительного принципала, который предоставил доступ к запрошенная область.

Я начинаю думать, что наиболее логичным местом для реализации этого является пользовательский атрибут, который я использую для украшения моих сервисов ServiceStack:

using ServiceStack.ServiceInterface;
using SpotAuth.Common.ServiceModel;

namespace SpotAuth.ResourceServer.Services {
    [RequireScope("hello")]
    public class HelloService : Service {
        public object Any(Hello request) {
            return new HelloResponse { Result = "Hello, " + request.Name };
        }
    }
}

Этот подход также позволит указать область (области), необходимые для каждого метода обслуживания. Тем не менее, похоже, что это скорее противоречит принципу "подключаемого" для OAuth2, так и к перехватчикам расширяемости, встроенным в модель ServiceStack AuthProvider.

Другими словами - я волнуюсь, я стуча в гвоздь с ботинком, потому что я не могу найти молоток...

Ответ 1

Обновление При дальнейшем размышлении ваша первоначальная мысль о создании атрибута RequiredScope была бы более чистым способом. Добавление его в конвейер ServiceStack так же просто, как добавление интерфейса IHasRequestFilter, реализация фильтра пользовательских запросов, как описано здесь: https://github.com/ServiceStack/ServiceStack/wiki/Filter-attributes

public class RequireScopeAttribute : Attribute, IHasRequestFilter {
  public void RequireScope(IHttpRequest req, IHttpResponse res, object requestDto)
  {
      //This code is executed before the service
      //Close the request if user lacks required scope
  }

  ...
}

Затем украсьте свой DTO или Services, как вы описали:

using ServiceStack.ServiceInterface;
using SpotAuth.Common.ServiceModel;

namespace SpotAuth.ResourceServer.Services {
    [RequireScope("hello")]
    public class HelloService : Service {
        public object Any(Hello request) {
            return new HelloResponse { Result = "Hello, " + request.Name };
        }
    }
}   

Пользовательский фильтр RequireScope будет почти идентичен реализации ServiceStack RequiredRoleAttribute., поэтому используйте его как отправную точку для кода.

В качестве альтернативы вы можете отобразить область действия для разрешения. Затем украсьте свой DTO или услугу соответствующим образом (см. Wiki для Windows):

[Authenticate]
[RequiredPermission("Hello")]
    public class HelloService : Service {
        public object Any(Hello request) {
            return new HelloResponse { Result = "Hello, " + request.Name };
        }
    }

Обычно ServiceStack вызывает метод bool HasPermission (разрешение строки) в IAuthSession. Этот метод проверяет, содержит ли разрешения списка List в IAuthSession требуемое разрешение, поэтому в пользовательской IAuthSession вы можете переопределить HasPermission и поместить туда области OAuth2.

Ответ 2

Хорошо, после многократного прохождения через различные библиотеки с помощью отладчика, я думаю, что вы делаете это так: https://github.com/dylanbeattie/OAuthStack

Есть две ключевые точки интеграции. Во-первых, атрибут настраиваемого фильтра, который используется на сервере для украшения конечных точек ресурсов, которые должны быть защищены с помощью авторизации OAuth2:

 /// <summary>Restrict this service to clients with a valid OAuth2 access 
/// token granting access to the specified scopes.</summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true)]
public class RequireOAuth2ScopeAttribute : RequestFilterAttribute {
    private readonly string[] oauth2Scopes;
    public RequireOAuth2ScopeAttribute(params string[] oauth2Scopes) {
        this.oauth2Scopes = oauth2Scopes;
    }

    public override void Execute(IHttpRequest request, IHttpResponse response, object requestDto) {
        try {
            var authServerKeys = AppHostBase.Instance.Container.ResolveNamed<ICryptoKeyPair>("authServer");
            var dataServerKeys = AppHostBase.Instance.Container.ResolveNamed<ICryptoKeyPair>("dataServer");
            var tokenAnalyzer = new StandardAccessTokenAnalyzer(authServerKeys.PublicSigningKey, dataServerKeys.PrivateEncryptionKey);
            var oauth2ResourceServer = new DotNetOpenAuth.OAuth2.ResourceServer(tokenAnalyzer);
            var wrappedRequest = new HttpRequestWrapper((HttpRequest)request.OriginalRequest);
            HttpContext.Current.User = oauth2ResourceServer.GetPrincipal(wrappedRequest, oauth2Scopes);
        } catch (ProtocolFaultResponseException x) {
            // see the GitHub project for detailed error-handling code
            throw;
        }
    }
}

Во-вторых, вот как вы подключаетесь к конвейеру HTTP-клиента ServiceStack и используете DotNetOpenAuth для добавления маркера OAuth2 Authorization: Bearer {key} к исходящему запросу:

// Create the ServiceStack API client and the request DTO
var apiClient = new JsonServiceClient("http://api.mysite.com/");
var apiRequestDto = new Shortlists { Name = "dylan" };

// Wire up the ServiceStack client filter so that DotNetOpenAuth can 
// add the authorization header before the request is sent
// to the API server
apiClient.LocalHttpWebRequestFilter = request => {
    // This is the magic line that makes all the client-side magic work :)
    ClientBase.AuthorizeRequest(request, accessTokenTextBox.Text);
}

// Send the API request and dump the response to our output TextBox
var helloResponseDto = apiClient.Get(apiRequestDto);

Console.WriteLine(helloResponseDto.Result);

Авторизованные запросы будут успешными; запросы с отсутствующим токеном, истекший токен или недостаточная область действия будут поднять WebServiceException

Это все еще очень много доказательств, но, похоже, работает очень хорошо. Я бы приветствовал обратную связь от тех, кто знает ServiceStack или DotNetOpenAuth лучше, чем я.