与所有主要提供商实施单点登录的最佳方式? [英] Best way to implement Single-Sign-On with all major providers?

查看:238
本文介绍了与所有主要提供商实施单点登录的最佳方式?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经对此主题进行了大量的研究,并且已经自己实施了很多解决方案。



包括OpenID,Facebook Connect(使用旧的Rest api和新的Graph Oauth 2.0 Api),使用twitter登录(据我所知,已经升级到现在已经完全符合OpenID),等等...



但是我仍然缺少的是完美的一个解决方案。



在我的研究期间,我沉迷了一些有趣的项目:





但是我不想依赖外部提供商,我想一个免费的解决方案,所以我没有限制在执行。



我也看到开发人员实施一个服务,另一个有义务跟随提供者的指导和设置模型和数据库



当然这样做会有效,但是它是一大堆工作,总是需要在应用程序中进行开发和更改等。



我正在寻找的是一个抽象层,将所有服务都放在一个可以集成到我的网站上的标准。一旦出现了新的服务,我只想添加一个模型来处理该特定提供者的抽象,所以我可以将其无缝集成到我的应用程序中。



或者更好的是找到一个已经存在的解决方案,我可以只是嫁妆。



理想情况下,这个抽象服务将独立于我的应用程序托管,因此可以用于多个应用程序并独立升级。 / p>

上述3个解决方案中的最后一个从概念中看起来很有希望。
一切都只是移植到一个合成的开放代码,而网站jut必须实现openid。



过了一段时间,我发现 Django socialauth ,一个基于python的认证系统Django Webframework。但是它看起来像上面所述的那样运行,我认为这是与Stackoverflow使用的(或至少一些修改的fork ...)相同的登录系统。



我下载它试图设置它,并看看它是否可以设置为一个独立的解决方案,但我没有运气,因为我也不是这样的python。



我会喜欢一个基于PHP的解决方案。



所以在这个长文本之后,我的问题正是:




  • 您将如何实现SSO,比移植所有内容并以OpenID为基础更好的想法?

  • 这个优点和缺点是什么?

  • 你知道现有的任何解决方案吗?优先开源。



我希望这个问题不是太主观,谢谢提前。



更新:
我得出结论,建立一个代理/包装器或你可以称之为Facebook,将其移植到一个openid,所以它是一个openid端点/提供者将是最好的选择。
这就是我所做的那样。



请看下面我的答案。



我添加了赏金得到反馈/讨论。 Maby我的方法不如我现在以为是那么好!

解决方案

在已经存在的答案的讨论之后,我总结:



几乎所有主要的提供商都是一个开放的提供商/终端,包括Google,Yahoo,Aol。



他们要求用户指定用户名来构建openid端点。
其中一些(上面提到的那些)确实有发现url,其中用户id被自动返回,以便用户只需要单击。 (如果有人可以解释技术背景,我会很高兴)



然而,屁股的唯一痛苦是Facebook,因为他们的Facebook连接在他们使用适应版本的OAuth进行身份验证。



现在我为我的项目做了什么是设置一个openid提供程序,用于使用我的Facebook应用程序的凭据来验证用户 - 所以用户获取连接到我的应用程序 - 并返回一个用户标识,如下所示:

  http:// my-facebook-openid-proxy- subdomain.mydomain.com/?id=facebook-user-id 

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



所以我的网站只需要实现opend id,我很好:)



我建立在你可以在这里找到的课程: http://gitorious.org/lightopenid



在我的index.php文件中,我只是叫它喜欢e:

 <?php 
require'LightOpenIDProvider.php';
需要FacebookProvider.php;
$ op = new FacebookProvider;
$ op-> appid = 148906418456860; //你的Facebook应用程序id
$ op-> secret ='mysecret'; //你的Facebook应用程序秘密
$ op-> baseurl ='http://fbopenid.2xfun.com'; //需要被facebook
$ op-> server()允许;
?>

,而FacebookProvider.php的源代码如下:

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

//我真的不知道这是什么。只是从示例中复制它。
public $ select_id = true;

函数__construct(){

$ this-> baseurl = rtrim($ this-> baseurl,'/'); //没有尾部斜杠,因为它将连接到
//请求uri wich有主要的斜杠

parent :: __ construct();

#如果我们使用select_id,我们必须禁用身份页面
#,以便RP可以发现它并获取正确的数据(即没有select_id)
if(isset ($ _GET ['id'])){
//我真的不知道这里发生了什么。有或没有作品!只是从示例中复制它。
$ this-> select_id = false;
}
}

函数设置($ identity,$ realm,$ assoc_handle,$ attributes)
{
//这里我们应该检查请求属性并相应调整范围参数
//现在我只是硬编码的电子邮件
$ 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)
{
//尝试验证
$ code = isset $ _GET [code])? $ _GET [code]:false;
如果(!$ code){
//用户尚未验证,则返回false,因此设置将其重定向到Facebook
返回false;
}

//我们有代码参数设置,看起来像用户认证
$ 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->电子邮件;
$ attribute_map = array(
'namePerson / friendly'=>'name',//我们应该解析Facebook链接以获取昵称
'contact / email'=>'电子邮件',
);

if($ id> 0){

$ requested_attributes = unserialize(base64_decode($ _ GET [attributes]));

//让我们很好,并返回一切我们可以
$ 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]};
}

//是认证的!
返回$ this-> serverLocation。 '?id ='。 $ id;
}
die('login failed'); //死,所以我们不要重试回到Facebook
return false;
}
函数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);
返回$数据;
}

}

它只是第一个工作版本快速和肮脏)
一些动态的东西是硬编码到我的需要。
应该显示如何和可以做到这一点。
如果有人拿起并改进或重新写入或者其他任何东西,我很高兴:)



我认为这个问题回答了



但我只是为了讨论而添加一个赏金。我想知道你对我的解决方案的看法。



我将奖励这个奖励给最好的答案/评论。


I already did a lot of research on this topic and have implemented a lot of solutions myself.

Including OpenID, Facebook Connect (using the old Rest api and the new Graph Oauth 2.0 Api), Sign in with twitter (wich has been upgraded to fully qualified Openid by now as far as I know), and so on...

But what I'm still missing is the perfect all in one solution.

During my research I stumpled about some interesting projects:

But I don't want to rely on an external provider and I would like a free solution as well, so I am not limited in implementation.

I have also seen developers implementing one service after another dutyfully following the providers instructinos and setting up models and database tables for everything.

Of course this will work but it is a shitload of work and always needs developement and changes in your application etc.

What I am looking for is an abstraction layer that takes all the services out there to one standard that can be integrated in my website. Once a new service appears I only want to add one model that deals with the abstraction of that specific provider so I can seamlessly integrate it into my application.

Or better, find an already existing solution that I can just dowonload.

Ideally this abstraction service would be hosted independently from my application so it can be used for several applications and be upgraded independently.

The last of the 3 solutions above looks promising from the concept. Everything is just ported to an synthetic openid, and the website jut has to implement openid.

After a while i found Django socialauth, a python based authentication system for the Django Webframework. But it looks like it operates as described above and i think this is the same login system that Stackoverflow uses (or at least some modified fork...).

I downloaded it and tried to set it up and to see whether it could be set up as a standalone solution but I had no luck, as I am not so into python either.

I would love a PHP based solution.

So after this long text my question precisely is:

  • How would you implement SSO, any better idea than porting everything and have OpenID as basis?
  • What are the pros and cons of that?
  • Do you know any already existing solutions? Preferrably open source.

I hope this question is not too subjective, thanks in advance.

Update: I concluded that building a proxy / wrapper or what you might call it for facebook, to port it to an openid so it beocmes an openid endpoint / provider would be the best option. So that exactly what i did.

Please see my answer below.

I added the bounty to get feedback/discussion on it. Maby my approach is not so good as i currently think it is!

解决方案

After the discussion of the already existing answer i sum up:

Almost every major provider is an openid provider / endpoint including Google, Yahoo, Aol.

Some of them requrie the user to specify the username to construct the openid endpoint. Some of them (the ones mentioned above) do have discovery urls, where the user id is automatically returned so that the user only has to click. (i would be glad if someone could explain the technical background)

However the only pain in the ass is Facebook, because they have their Facebook connect where they use an adapted version of OAuth for authentication.

Now what I did for my project is to set up an openid provider that authenticates the user with the credentials of my facebook Application - so the user gets connected to my application - and returns a user id that looks like:

http://my-facebook-openid-proxy-subdomain.mydomain.com/?id=facebook-user-id

I also configured it to fetch email adress and name and return it as AX attributes.

So my website just has to implement opend id and i am fine :)

I build it upon the classes you can find here: http://gitorious.org/lightopenid

In my index.php file i just call it like this:

<?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();
?>

and the source code of FacebookProvider.php follows:

<?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;
    }    

}

Its just a first working version (quick and dirty) Some dynamic stuff is hardcoded to my needs. It should show how and that it can be done. I am happy if someone picks up and improves it or re writes it or whatever :)

Well i consider this question answered

but I add a bounty just to get discussion. I would like to know what you think of my solution.

I will award the bounty to the best answer/comment beside this one.

这篇关于与所有主要提供商实施单点登录的最佳方式?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆