使用OWIN Identity注册多个API客户端的Web API 2外部登录

75

我希望使用以下架构(我为此示例编造了产品名称):

在一个服务器上运行的Web API 2应用程序 http://api.prettypictures.com

在另一台服务器上运行的MVC 5客户端应用程序 http://www.webpics.com

我希望www.webpics.com客户端应用程序使用Pretty Pictures API来执行以下操作:

  • 使用用户名和密码注册新帐户
  • 使用Facebook/Google/Twitter/Microsoft注册新帐户
  • 登录
  • 检索图片

以上所有内容都可以正常工作,除了使用Facebook、Google等外部帐户注册。

我无法找出从单独的API客户端用户创建外部帐户的正确流程。

我已经研究了大多数可用的身份验证流文档,例如: enter image description here

我已经阅读了关于OWIN中新标识模型的几乎所有内容。

我已经检查了Visual Studio 2013中的SPA模板。它演示了如何做到我需要的大多数操作,但仅当客户端和API在同一主机上时;如果我想让多个客户端访问我的API,并能够让用户通过Google等进行注册,则不起作用,据我所知,OWIN身份验证流会中断。

以下是目前的流程:

  • 用户浏览至www.webpics.com/Login
  • www.webpics.com调用api.prettypictures.com/Account/ExternalLogins(并将returnUrl设置为返回到回调www.webpics.com),并向用户显示结果链接
  • 用户点击“Google”
  • 浏览器将重定向到api.prettypictures.com/Account/ExternalLogin并带上提供程序的名称等信息。
  • API的ExternalLogin操作会向google.com发起挑战。
  • 浏览器被重定向到google.com
  • 用户输入他们的用户名和密码(如果他们还没有登录google.com)。
  • google.com现在呈现安全许可证:"api.prettypictures.com"需要访问您的电子邮件地址、姓名、妻子、孩子等是否可以?"
  • 用户点击“是”,并返回到api.prettypictures.com/Account/ExternalLogin,带有Google设置的cookie。
  • 这就是我目前遇到的问题。接下来应该发生的是,客户端应用程序应该收到通知,表明用户已经成功通过google.com进行了身份验证,并获得一个单一使用的访问代码,以便稍后交换为访问令牌。如果必要,客户端应用程序应该有机会提示用户输入与其google.com登录相关联的用户名。

    我不知道如何实现这一点。

    事实上,在此时,浏览器最终停留在来自Google回调的api.prettypictures.com/Account/ExternalLogin端点处。API已经针对Google进行了登录,但客户端不知道如何处理它。我应该将那个cookie返回到www.webpics.com吗?

    在SPA应用程序中,通过AJAX完成,并且google.com会返回一个作为URL片段的令牌,这样它就可以很好地工作,因为它都位于一个域上。但是,这违背了拥有多个完全使用的“API”客户端的目的。

    救命啊!


嘿,乔希!我目前也在处理这个问题。我们有一个Web Api和一个html5/angularJS SPA,我们想使用Google/Facebook进行身份验证。你没有一个博客或者Github仓库,展示一下你是如何解决这个问题的吗?我非常感兴趣! - Ashkan Hovold
抱歉,Ashkan,我没有!你的SPA是否在与Web API(如上所述)不同的域上运行? - joshcomley
是的,我们正在开发一个PhoneGap应用程序,因此我们有一个Web API作为我们的后端,并且在不同的域上有一个纯HTML5 / AngularJS SPA作为我们的前端。这将成为以后从用户手机访问API的应用程序。 - Ashkan Hovold
Josh或@AshkanAldini,你们解决了这个问题吗?我正在尝试做类似的事情,Pinpoint的答案很有帮助,但我仍然对实现感到困惑。 - mayabelle
1个回答

46
更新:自我在一月份撰写这篇文章以来,情况已经发生了变化:MSFT发布了他们的官方OpenID连接客户端中间件,并且我和@manfredsteyer一起努力将Katana中构建的OAuth2授权服务器适应OpenID connect。这种组合结果是一个更容易、更强大的解决方案,不需要任何自定义客户端代码,并且与标准的OAuth2/OpenID connect客户端100%兼容。我在一月份提到的不同步骤现在可以用几行代码替换:

服务器:

app.UseOpenIdConnectServer(options =>
{
    options.TokenEndpointPath = new PathString("/connect/token");
    options.SigningCredentials.AddCertificate(certificate);

    options.Provider = new CustomOpenIdConnectServerProvider();
});

客户端:

app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
    Authority = "http://localhost:55985/",

    ClientId = "myClient",
    ClientSecret = "secret_secret_secret",
    RedirectUri = "http://localhost:56854/oidc"
});

你可以在 GitHub 存储库中找到所有详细信息(以及不同的示例):

https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server

https://github.com/aspnet-contrib/AspNet.Security.OpenIdConnect.Server/tree/dev/samples/Nancy


Josh,你肯定走在了正确的道路上,你的 委托/联合身份验证 实现看起来非常不错(我想你可能使用了预定义的OWIN中间件Microsoft.Owin.Security.Facebook/Google/Twitter)。

你需要做的是创建自己的自定义 OAuth2 授权服务器。你有很多选择实现它,但最简单的方法可能是将 OAuthAuthorizationServerMiddleware 插入到你的 OWIN 启动类中。你可以在 Microsoft.Owin.Security.OAuth Nuget 包中找到它。

虽然最佳实践是创建一个单独的项目(通常称为“AuthorizationServer”),但当它不打算在多个 API 中使用时,我个人更喜欢将其添加到我的“API 项目”中(在这里,你需要将其插入到托管“api.prettypictures.com”的项目中)。

你可以在 Katana 存储库中找到一个很棒的示例:

https://katanaproject.codeplex.com/SourceControl/latest#tests/Katana.Sandbox.WebServer/Startup.cs

app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
    AuthorizeEndpointPath = new PathString("/oauth2/authorize"),
    TokenEndpointPath = new PathString("/oauth2/token"),
    ApplicationCanDisplayErrors = true,

    AllowInsecureHttp = true,

    Provider = new OAuthAuthorizationServerProvider
    {
        OnValidateClientRedirectUri = ValidateClientRedirectUri,
        OnValidateClientAuthentication = ValidateClientAuthentication,
        OnGrantResourceOwnerCredentials = GrantResourceOwnerCredentials,
    },
    AuthorizationCodeProvider = new AuthenticationTokenProvider
    {
        OnCreate = CreateAuthenticationCode,
        OnReceive = ReceiveAuthenticationCode,
    },
    RefreshTokenProvider = new AuthenticationTokenProvider
    {
        OnCreate = CreateRefreshToken,
        OnReceive = ReceiveRefreshToken,
    }
});

请浏览整个项目,了解授权同意表单如何使用简单的Razor文件实现。如果您喜欢像ASP.NET MVC或NancyFX这样的更高级框架,请创建自己的AuthorizationController控制器和Authorize方法(确保接受GET和POST),并使用属性路由来匹配在OAuth2授权服务器中定义的AuthorizeEndpointPath(例如,在我的示例中为[Route("oauth2/authorize")],我已更改AuthorizeEndpointPath以使用oauth2/作为路径基础)。
另一件事是在您的Web应用程序中添加OAuth2授权客户端。不幸的是,Katana中没有通用的OAuth2客户端支持,您将不得不构建自己的客户端。我个人向Katana团队提交了一个提案,但被拒绝了。但是不要惊慌,这很容易做到:

https://katanaproject.codeplex.com/SourceControl/latest#src/Microsoft.Owin.Security.Google/GoogleOAuth2AuthenticationHandler.cs下载适当的文件:

您需要 GoogleOAuth2AuthenticationHandler, GoogleOAuth2AuthenticationMiddleware, GoogleOAuth2AuthenticationOptions, GoogleAuthenticationExtensions(您将不得不删除与 Google OpenID 实现相对应的前两个方法),IGoogleOAuth2AuthenticationProvider, GoogleOAuth2ReturnEndpointContext, GoogleOAuth2AuthenticationProvider, GoogleOAuth2AuthenticatedContextGoogleOAuth2ApplyRedirectContext。将这些文件插入到您托管“webpics.com”的项目中后,相应地重命名它们,并在 GoogleOAuth2AuthenticationHandler 中更改授权和访问令牌端点 URL,以匹配您在 OAuth2 授权服务器中定义的端点。

接下来,将重命名/自定义的GoogleAuthenticationExtensions的Use方法添加到OWIN启动类中。建议使用AuthenticationMode.Active,以便您的用户直接重定向到您的API OAuth2授权终点。因此,您应该抑制“api.prettypictures.com/Account/ExternalLogins”往返,并让OAuth2客户端中间件将401响应改为重定向客户端到您的API。

祝好运。如果您需要更多信息,请不要犹豫;)。


我尝试将状态作为查询字符串参数放入其中,但中间件似乎只是吞噬它并将我重定向到客户端主页。 - joshcomley
太好了!我不知道你是否解决了上一个问题,但我想在传递给IAuthenticationManager.Challenge的AuthenticationProperties上设置RedirectUri就可以解决问题了 ;) - Kévin Chalet
+1 对于 AuthenticationMode=AuthenticationMode.Active,解决了我的问题,即在回调中 User.Identity.IsAuthenticated 总是为 false(即外部 Cookie 没有被设置)。 - Dunc
1
@mayabelle:好的,请看我在这里的回答:https://dev59.com/N14b5IYBdhLWcg3w9Fyr#28488773 - Kévin Chalet
1
@CularBytes 我会看一下你的其他“POST”。 - Worthy7
显示剩余9条评论

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