如何最佳实现与所有主要供应商的单点登录?

42
我已经对这个话题进行了大量研究,并自己实施了许多解决方案,包括OpenID、Facebook Connect(使用旧版Rest API和新版Graph OAuth 2.0 API)、Sign in with twitter(据我所知,现在已完全升级为合格的OpenID),等等...
但我仍然缺少完美的一站式解决方案。在我的研究中,我偶然发现了一些有趣的项目:
- Janrain(以前是RPX)-一种商业解决方案 - Gigya-一个免费但外部托管的解决方案,具有JavaScript和REST API - AnyOpenID-客户端的免费解决方案,网站的商业解决方案 但我不想依赖外部提供者,也希望有一个免费的解决方案,这样我就不会受到实现的限制。
我还看到过开发人员按照提供者的说明一个接一个地实施服务,并为所有东西设置模型和数据库表。
当然,这样做可以实现,但需要大量的工作,并且始终需要应用程序的开发和更改等。
我要找的是一个抽象层,将所有现有的服务转换为一个标准,可以集成到我的网站中。一旦出现新的服务,我只想添加一个处理该特定提供者抽象的模型,这样我就可以将其无缝地集成到我的应用程序中。
或者更好的是,找到一个已经存在的解决方案,我只需要下载即可。
理想情况下,这个抽象服务应该独立于我的应用程序托管,这样它可以用于多个应用程序,并且可以独立升级。
上述3种解决方案中的最后一种看起来很有前途。所有内容都被移植到合成OpenID中,网站只需实现OpenID即可。
过了一会儿,我发现了Django socialauth,这是一个基于Python的Django Web框架的身份验证系统。但是它看起来像是按照上述描述运作的,而且我认为这是Stackoverflow使用的同一登录系统(或者至少是某个修改版...)。
我下载了它并尝试设置它,看看它是否可以作为一个独立的解决方案设置,但我没有成功,因为我对Python也不是很熟悉。
我希望有一个基于PHP的解决方案。
所以在这篇长文之后,我的问题是:
你会如何实现SSO,除了移植一切并以OpenID为基础,还有更好的想法吗?
这样做的利弊是什么?
您知道任何已经存在的解决方案吗?最好是开源的。
希望这个问题不太主观,提前感谢。
更新: 我得出结论,为Facebook构建一个代理/包装器或者你可以称之为OpenID端点/提供程序的OpenID,是最好的选择。 所以我就这样做了。
请参见下面的答案。

我增加了悬赏以获得反馈/讨论。也许我的方法并不像我当前认为的那样好!


我稍微修改了我的类并修复了一些错误!完成后会将其放在GitHub或其他地方,收藏此问题以获取更新。 - The Surrican
1
不用理会@markus-tharkun,我感觉在SO上实际上互相帮助已经变得不重要了。这更像是一个拥有自己定义优秀问题的标准和愿望的政权,追捕并羞辱提问者,以使其永远不再返回。 - The Surrican
1
这并不完全适用于这个问题,但有很多问题被关闭,因为“不够具体”。在我看来,这是因为某人需要帮助实现某些东西的本质。如果一个人已经确切知道要问什么,那么这个问题就变得无关紧要或者自己可以轻松回答了... - The Surrican
3个回答

13
作为此答案的原始作者,我想指出我认为它已经过时了。由于大多数提供商决定专门实现Oauth而不是Openid。新的Openid服务也可能使用基于oauth的openid connect。有一些很好的库,例如:https://github.com/hybridauth/hybridauth 在讨论了之前的回答后,我总结如下:
几乎每个主要提供商都是openid提供商/终端,包括Google、Yahoo和Aol。
其中一些要求用户指定用户名来构建openid终端点。其中一些(上面提到的那些)确实有发现URL,其中用户ID会自动返回,以便用户只需单击即可。(如果有人能解释一下技术背景,我会很高兴。)
然而,唯一麻烦的是Facebook,因为他们有他们的Facebook连接,其中他们使用OAuth的适应版本进行身份验证。
现在,我为我的项目设置了一个Openid提供程序,该提供程序使用我的Facebook应用程序的凭据对用户进行身份验证-因此用户与我的应用程序连接,并返回一个看起来像这样的用户ID:
http://my-facebook-openid-proxy-subdomain.mydomain.com/?id=facebook-user-id

我还配置了它来获取电子邮件地址和名称,并将其作为AX属性返回。

所以我的网站只需要实现OpenID,就可以正常工作 :)

我是使用这里的类构建的:http://gitorious.org/lightopenid

在我的index.php文件中,我只需像这样调用它:

<?php
require 'LightOpenIDProvider.php';
require 'FacebookProvider.php';
$op = new FacebookProvider;
$op->appid = 148906418456860; // your facebook app id
$op->secret = 'mysecret'; // your facebook app secret
$op->baseurl = 'http://fbopenid.2xfun.com'; // needs to be allowed by facebook
$op->server();
?>

而FacebookProvider.php的源代码如下:

<?php
class FacebookProvider extends LightOpenIDProvider
{
    public $appid = "";
    public $appsecret = "";
    public $baseurl = "";

    // i have really no idea what this is for. just copied it from the example.
    public $select_id = true;

    function __construct() {

        $this->baseurl = rtrim($this->baseurl,'/'); // no trailing slash as it will be concatenated with
                                                    // request uri wich has leading slash

        parent::__construct();

        # If we use select_id, we must disable it for identity pages,
        # so that an RP can discover it and get proper data (i.e. without select_id)
        if(isset($_GET['id'])) {
            // i have really no idea what happens here. works with or without! just copied it from the example.
            $this->select_id = false;
        }
    }

    function setup($identity, $realm, $assoc_handle, $attributes)
    {
        // here we should check the requested attributes and adjust the scope param accordingly
        // for now i just hardcoded email
        $attributes = base64_encode(serialize($attributes));    

        $url = "https://graph.facebook.com/oauth/authorize?client_id=".$this->appid."&redirect_uri=";

        $redirecturl = urlencode($this->baseurl.$_SERVER['REQUEST_URI'].'&attributes='.$attributes);
        $url .= $redirecturl;
        $url .= "&display=popup";
        $url .= "&scope=email";
        header("Location: $url");
        exit();        

    }

    function checkid($realm, &$attributes)
    {
        // try authenticating
        $code = isset($_GET["code"]) ? $_GET["code"] : false;
        if(!$code) {
            // user has not authenticated yet, lets return false so setup redirects him to facebook
            return false;
        }

        // we have the code parameter set so it looks like the user authenticated
        $url = "https://graph.facebook.com/oauth/access_token?client_id=148906418456860&redirect_uri=";

        $redirecturl = ($this->baseurl.$_SERVER['REQUEST_URI']);
        $redirecturl = strstr($redirecturl, '&code', true);
        $redirecturl = urlencode($redirecturl);     
        $url .= $redirecturl;
        $url .= "&client_secret=".$this->secret;
        $url .= "&code=".$code;
        $data = $this->get_data($url);

        parse_str($data,$data);

        $token = $data['access_token'];

        $data = $this->get_data('https://graph.facebook.com/me?access_token='.urlencode($token));
        $data = json_decode($data);

        $id = $data->id;
        $email = $data->email;
        $attribute_map = array(
            'namePerson/friendly' => 'name', // we should parse the facebook link to get the nickname
            'contact/email' => 'email',
        );

        if($id > 0) {

            $requested_attributes = unserialize(base64_decode($_GET["attributes"]));

            // lets be nice and return everything we can
            $requested_attributes = array_merge($requested_attributes['required'],$requested_attributes['optional']);
            $attributes = array();
            foreach($requested_attributes as $requsted_attribute) {
                if(!isset($data->{$attribute_map[$requsted_attribute]})) {
                    continue; // unknown attribute
                }
                $attributes[$requsted_attribute] = $data->{$attribute_map[$requsted_attribute]};    
            }

            // yeah authenticated!
            return $this->serverLocation . '?id=' . $id ;
        }
        die('login failed'); // die so we dont retry bouncing back to facebook
        return false;
    }
    function get_data($url) { 
      $ch = curl_init();
      $timeout = 5;
      curl_setopt($ch,CURLOPT_URL,$url);
      curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
      curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,$timeout);
      $data = curl_exec($ch);
      curl_close($ch);
      return $data;
    }    

}

这只是第一个工作版本(快速而粗略)。 某些动态内容已经硬编码到我的需求中。 它应该展示如何以及可以做到。 如果有人拿起它并改进它或重新编写它,我会很高兴的:)

好吧,我认为这个问题已经得到了回答。

但我添加了赏金只是为了得到讨论。 我想知道您对我的解决方案的看法。

我将把奖励授予除此之外最佳的答案/评论。


1
将appid存储为字符串可能更安全: $op->appid ='148906418456860'; 这是一个巨大的、危险的整数。 - ͢bts
常见的一个“错误”.. 不过我倾向于抛出“请升级到64位”的异常 :P - The Surrican
我认为值得一提的是,在回答创建时,它基本上是正确的,然而历史的进程使得OAuth成为OpenID的胜利者...因此,更好的做法可能是专注于OAuth。 - The Surrican

5

对于这个应用程序,OpenID将是您最好的选择。它受到许多提供商的支持:

  • Google
  • Yahoo
  • MyOpenID
  • AOL

唯一的问题是Twitter尚未实现OpenID。这可能是因为他们是一家专有的公司,所以他们想要自己的解决方案。

为了解决这个问题,您可以编写一个包装类来提供与OpenID的兼容性,但即使您的用户没有Twitter帐户,他们可能拥有Facebook、Google或Yahoo帐户。

Facebook支持oauth,因此您需要将oauth转换为OpenID。

一些OpenID的PHP库可以在这里找到。

现在,关于Facebook成为oauth提供者的一些问题已经被提出。

他们的oauth URL是"https://graph.facebook.com/oauth/authorize"

如果你仍然不相信我,那么你可以看看这个javascript文件,我从中得到了那个URL。如果你不相信那个javascript文件,那么请注意它是由stackexchange托管的,这个网站的提供者。现在你必须相信了。


也许我忽略了什么,但在涉及Facebook和OpenID时,我觉得唯一的可能性是将OpenId帐户链接到您的Facebook帐户。我想要的是人们可以使用他们的Facebook帐户在我的网站上进行身份验证。我没有找到任何相关资源?也许你可以指点我正确的方向或提供一个链接? - The Surrican
Facebook是一个OpenID提供者。我在答案中发布了一个链接,如果你查看提供者列表,Facebook就是其中的一个链接。 - xaav
我已经编辑了问题,以使其更清楚地涉及Facebook。 - xaav
2
@geoff:OpenID!= OAuth。例如,尝试使用您发布的Facebook“openid”网址对stackoverflow.com进行身份验证。 - zerkms
zermks是正确的。我的猜测是stackoverflow基本上正在实现oauth 2.0,但通过成为一个openid提供者来进行端口转移...有点像anyopenid。我从未设置过openid提供者,我认为这是相当困难的,但我确信这样的实现必须在某个地方可用... - The Surrican
抱歉造成困惑。Zerkms是正确的。我被Facebook发布的支持OpenID的帖子所迷惑了。看起来你将不得不自己实现它。请参见这里:http://stackoverflow.com/questions/76184/php-tutorial-for-openid-and-oauth - xaav

2
快进两年,"OpenID是答案"的回答似乎被一些大型提供商所抛弃。大多数主要的第三方集成网站似乎已经转向某种OAuth(通常是OAuth2)。此外,如果您不介意不使用OpenID/OAuth,现在有一个完整的PHP编写的SSO解决方案(免责声明和全面披露:这个产品是由我在CubicleSoft旗下开发和维护的):Single Sign-On Server/Client。当最初提出这个问题时,它并不存在。它有一个自由许可证(MIT或LGPL),并满足您的抽象层要求。该项目往往专注于企业签名,但也涉及一些社交媒体签名(Google和Facebook)。您还可以看看HybridAuth,它只关注社交媒体的登录,但更像是一个库,而不是一个预构建的解决方案,你需要做更多的工作来设置它。这真的取决于你想要什么。如果您对您的OpenID解决方案感到满意,那就太好了,但今天比两年前有更多的选择,人们仍然在发现这个线程。

感谢您发布答案!请务必仔细阅读有关自我推广的FAQ。特别注意,每次链接到您自己的网站/产品时,必须发布免责声明,并且您在这里的主要原因不应该是促销您的产品/网站。 - Andrew Barber

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