12

我已经使用 DotNetOpenAuth 创建了一个 OAuth2 授权服务器,它工作正常 - 我正在使用资源所有者密码流,并成功地将用户凭据交换为访问令牌。

我现在想使用该访问令牌从 ServiceStack API 中的安全端点检索数据,但我不知道该怎么做。我检查了 ServiceStack 中包含的 Facebook、Google 等提供商,但不清楚我是否应该遵循相同的模式。

我想要实现的(我认为!)是

  1. OAuth客户端(我的应用程序)向资源所有者('Catherine Smith')询问凭据
  2. 客户端向授权服务器提交请求,接收访问令牌
  3. 客户端资源服务器请求安全资源( ) GET /users/csmith/photos
    • 访问令牌包含在HTTP 标头中,例如 Authorization: Bearer 1234abcd...
  4. 资源服务器解密访问令牌以验证资源所有者的身份
  5. 资源服务器检查资源所有者是否有权访问请求的资源
  6. 资源服务器资源返回给客户端

第 1 步和第 2 步正在工作,但我不知道如何将 DotNetOpenAuth 资源服务器代码与 ServiceStack 授权框架集成。

有没有一个例子说明我将如何实现这一目标?我在How to buildsecure api using ServiceStack as resource server with OAuth2.0 中找到了类似的 StackOverflow 帖子?但它不是一个完整的解决方案,并且似乎没有使用 ServiceStack 授权提供者模型。

编辑:更详细一点。这里有两个不同的网络应用程序。一个是身份验证/授权服务器 - 它不托管任何客户数据(即没有数据 API),但公开了 /oauth/token 方法,该方法将接受用户名/密码并返回 OAuth2 访问令牌和刷新令牌,以及提供令牌刷新功能。这是基于 ASP.NET MVC 构建的,因为它与 DotNetOpenAuth 中包含的 AuthorizationServer 示例几乎相同。这可能会在以后被替换,但现在它是 ASP.NET MVC。

对于实际的数据 API,我使用的是 ServiceStack,因为我发现它比 WebAPI 或 MVC 在公开 ReSTful 数据服务方面要好得多。

所以在下面的例子中:

序列图

Client是运行在用户本地机器上的桌面应用程序,Auth服务器是 ASP.NET MVC + DotNetOpenAuth,Resource 服务器是 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 请求中的 Authorization 标头是否代表已授予对所请求范围的访问权限的有效主体.

我开始认为实现这一点最合乎逻辑的地方是在我用来装饰我的 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 模型中内置的可扩展性挂钩完全相反。

换句话说-我担心我会用鞋子敲钉子,因为我找不到锤子...

4

2 回答 2

8

好的,在使用调试器对各种库进行大量单步执行之后,我认为您可以这样做: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;
        }
    }
}

其次,这是您连接到 ServiceStack HTTP 客户端管道并使用 DotNetOpenAuth 将 OAuth2Authorization: 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 的人提供反馈。

于 2013-08-22T16:28:14.433 回答
6

更新 进一步思考,您最初的想法是创建一个 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 或服务:

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 或服务(有关详细信息,请参阅 SS wiki),例如:

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

通常 ServiceStack 在 IAuthSession 中调用方法 bool HasPermission(string permission)。此方法检查 IAuthSession 中的 List Permissions 列表是否包含所需的权限,因此,在自定义 IAuthSession 中,您可以覆盖 HasPermission 并将您的 OAuth2 范围检查在那里。

于 2013-08-19T21:03:30.223 回答