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

12

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

这是我想要实现的目标:

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

我的问题在于,内置功能似乎都将身份验证和授权作为一个流程完成。例如,如果我在Startup.Auth.cs中设置以下内容:

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

...然后从我的网络浏览器导航到此URL:

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 URL被埋藏在某个地方)。此外,我认为我需要一种根据授权代码单独请求第三方令牌的方法。

这篇帖子似乎与我正在尝试做的事情有些相似:

使用OWIN身份验证注册来自多个API客户端的Web API 2外部登录

...但那里的解决方案似乎需要客户端是MVC应用程序,这不一定适用于我。我想在客户端上尽可能简单,按照我上面的图表流程进行操作,但同时也不要重新发明轮子(尽可能重用OWIN/OAuth2实现中已经存在的内容)。理想情况下,我不希望客户端必须引用任何OWIN/OAuth库,因为我真正需要客户端做的只是访问API提供的外部URL(在我的示例中为Dropbox),允许用户输入其凭据并授予权限,并将结果授权代码发送回API。

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

1个回答

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

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承载身份验证中间件并实现自定义来手动验证由Dropbox发出的不透明令牌的另一种选择。与提到的示例(在Dropbox和MVC客户端应用程序之间充当授权代理服务器的示例)不同,JS应用程序直接注册到Dropbox。
您将不得不对Dropbox个人资料终端节点(https://api.dropbox.com/1/account/info)发出请求以使用接收到的令牌进行验证,并为API接收到的每个请求构建适当的实例。这是一个示例(但请不要直接使用它,因为它没有经过测试)。
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://dev59.com/P2Qm5IYBdhLWcg3w6SdR#17439317。祝你好运,如果您仍需要帮助,请不要犹豫。

感谢您的帮助!非常抱歉,我对OAuth还不太熟悉,仍然感到困惑。它是如何分离身份验证和授权部分的呢?也就是说,我需要的两个部分是:1)当客户端传递一个提供程序名称(字符串)时,我该如何向客户端返回身份验证URL?... 2)当客户端传递授权代码到API时,我该如何将其发送给内置的OAuth代码以完成授权并获取令牌? - mayabelle
我认为你的答案涵盖了如何处理客户端已经拥有令牌并将令牌发送到API执行操作后的后续调用。这非常有帮助,因为这将是我的下一步。但现在我卡在了早期阶段,即将令牌传递给客户端的流程(客户端向API发送提供程序名称,API返回Dropbox URL,客户端导航到Dropbox URL,在那里用户输入凭据并获取授权代码,客户端将授权代码发送到API,API调用Dropbox以交换代码获取令牌,API将令牌返回给客户端)。 - mayabelle
好的,谢谢您澄清。如果我需要使用代码流程,它会是什么样子?那会是选项2吗?如果是,那看起来是什么样子? - mayabelle
如果您更喜欢使用代码流与Dropbox进行通信,则需要服务器端代码来交换授权码和访问令牌(凭据必须保持私密)...这看起来像是您想要避免的东西:https://github.com/aspnet-security/AspNet.Security.OpenIdConnect.Server/blob/dev/samples/Mvc/Mvc.Server/Startup.cs :) - Kévin Chalet
好的。我认为我仍在努力确定使用哪种流程是有意义的。听起来你建议我在我的情况下使用隐式流程 - 为什么?一个与另一个相比的优点是什么? - mayabelle
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接