OWIN / OAuth2第三方登录:来自客户端应用程序的身份验证,来自Web API的授权

时间:2015-02-12 20:55:45

标签: authentication asp.net-web-api oauth oauth-2.0 owin

我正在尝试创建一个Web API,允许API的客户端(本机移动应用程序)使用第三方云存储提供程序进行登录。我使用了以下来自Microsoft的一般流程:

这是我想要实现的目标:

我使用带有外部身份验证的默认ASP.NET Web API Visual Studio模板,以及用于Dropbox登录功能的OWin.Security.Providers Nuget包,以及用于Google(云端硬盘)和Microsoft的现有内置登录功能(OneDrive)。

我遇到的问题是,内置功能似乎都是作为一个流程的一部分进行身份验证和授权。例如,如果我在Startup.Auth.cs中设置以下内容:

DropboxAuthenticationOptions dropboxAuthOptions = new DropboxAuthenticationOptions
                                                    {
                                                        AppKey = _dropboxAppKey,
                                                        AppSecret = _dropboxAppSecret
                                                    };
app.UseDropboxAuthentication(dropboxAuthOptions);

...并从我的网络浏览器导航到此网址:

http://<api_base_url>/api/Account/ExternalLogin?provider=Dropbox&response_type=token&client_id=self&redirect_uri=<api_base_url>

我已成功重定向到Dropbox登录:

https://www.dropbox.com/1/oauth2/authorize?response_type=code&client_id=<id>&redirect_uri=<redirect_uri>

...然后在我授予访问权限后,重定向回到:

http://<api_base_url>/Help#access_token=<access_token>&token_type=bearer&expires_in=1209600

...因为你可以看到令牌是其中的一部分,所以可以提取。问题是客户端需要导航到Dropbox并将授权代码返回到Web API,Web API会将授权代码发送回第三方以获取令牌,然后将其返回给客户......如上图所示。我需要在AccountController中使用ExternalLogin操作以某种方式检索Dropbox url并将其返回给客户端(它只是一个json响应),但我没有看到检索它的方法(它只返回ChallengeResult,并且实际的Dropbox网址被埋在某处。此外,我认为我需要一种方法来根据授权代码单独请求来自第三方的令牌。

这篇文章似乎与我想要做的有点类似:

Registering Web API 2 external logins from multiple API clients with OWIN Identity

...但是解决方案似乎要求客户端成为MVC应用程序,对我来说不一定是这种情况。我希望在客户端保持尽可能简单,遵循上图中的流程,但也不要重新发明轮子(尽可能多地重用OWIN / OAuth2实现中已存在的内容)。理想情况下,我不希望客户端必须引用任何OWIN / OAuth库,因为我真正需要客户端做的就是访问API提供的外部URL(在我的示例中为Dropbox),具有用户输入他们的凭据并给予许可,并将生成的授权代码发送回api。

从概念上讲,这听起来并不难,但我不知道如何实现它,仍然尽可能多地使用现有的OAuth代码。请帮忙!

1 个答案:

答案 0 :(得分:4)

要明确的是,我发布的链接中提到的示例可以与任何OAuth2客户端一起使用,使用任何支持的流(隐式,代码或自定义)。当与您自己的授权服务器通信时,如果您想使用JS或移动应用程序,您当然可以使用隐式流程:您只需使用response_type=token构建授权请求并从URI片段中提取访问令牌JS方面。

http://localhost:55985/connect/authorize?client_id=myClient&redirect_uri=http%3a%2f%2flocalhost%3a56854%2f&response_type=token

供参考,以下是示例:https://github.com/aspnet-security/AspNet.Security.OpenIdConnect.Server/tree/dev/samples/Mvc/Mvc.Server


如果您更喜欢更简单的方法(不涉及自定义OAuth2授权服务器),这里是使用OAuth2承载身份验证中间件并实现自定义IAuthenticationTokenProvider以手动验证Dropbox发出的不透明令牌的另一个选项。与上述示例(类似于Dropbox和MVC客户端应用程序之间的授权代理服务器)不同,JS应用程序直接在Dropbox中注册。

您必须使用收到的令牌对Dropbox配置文件端点(https://api.dropbox.com/1/account/info)发出请求以验证它,并为您的API收到的每个请求构建一个足够的ClaimsIdentity实例。这是一个样本(但请不要按原样使用,它尚未经过测试):

public sealed class DropboxAccessTokenProvider : AuthenticationTokenProvider {
    public override async Task ReceiveAsync(AuthenticationTokenReceiveContext context) {
        using (var client = new HttpClient()) {
            var request = new HttpRequestMessage(HttpMethod.Get, "https://api.dropbox.com/1/account/info");
            request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.Token);

            var response = await client.SendAsync(request);
            if (response.StatusCode != HttpStatusCode.OK) {
                return;
            }

            var payload = JObject.Parse(await response.Content.ReadAsStringAsync());

            var identity = new ClaimsIdentity("Dropbox");
            identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, payload.Value<string>("uid")));

            context.SetTicket(new AuthenticationTicket(identity, new AuthenticationProperties()));
        }
    }
}

您可以通过AccessTokenProvider属性轻松插入它:

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions {
    AccessTokenProvider = new DropboxAccessTokenProvider()
});

它有其自身的缺点:它需要缓存以避免泛滥Dropbox端点,如果您想接受由不同提供商(例如Dropbox,Microsoft,Google,Facebook)发布的令牌,则不是正确的方法。

更不用说如果提供非常低的安全级别:因为您无法验证访问令牌的受众(即令牌被发送到的一方),您无法确保访问令牌是发给您完全信任的客户端应用程序,这允许任何第三方开发人员使用他自己的Dropbox令牌与您的API,而无需征得用户的同意。

这显然是一个主要的安全问题,这就是为什么你应该更喜欢链接样本中使用的方法。你可以在这个帖子上阅读更多关于混乱的副攻击:https://stackoverflow.com/a/17439317/542757

祝你好运,如果你还需要帮助,请不要犹豫。