• 认识单点登录


     

    初识单点登录

    最初接触到单点登录要追溯到3年多以前了,那时候看到的只是passport,当时要做全国所有社区的登录,然后就照着内部文档写了代码,然后就接入了(这里要提一句是百度与腾讯一旦形成产品的技术项目,文档都很不错)然后就没有然后了......

    而知识的珍贵程度却是这样的:

    知识珍贵度排名:
    听过 < demo过 < 实际工作用过 < 实际工作中被坑过< 实际工作中被多次坑过或者深入研究总结过

    所以,脱离工作去学习node可能收效不大,脱离项目去说前端优化可能不让人信服,不被ie坑也认识不到兼容,这个时候我们就要抓住工作中的点,一点点深入进去了,也许这一轮的瓶颈就自然而然的突破了呢,这是我最近的想法,也试着朝着相关的方向努力。

    今天想要写passport事实上是工作中用到了这个点了,如果我就照着内部文档接入了似乎意义不大,所以我准备就着这个机会骚骚了解下原理,期望将他变成第一类知识沉淀。

    文中内容皆是自我总结或者查询,如果有误请您提出,文中代码比较简陋,仅帮助理解,勿用作实际项目

    什么是单点登录

    单点登录即single sign on,用以解决同一公司不同子产品之间身份认证问题,也就是说,a.baidu.com与b.baidu.com两个站点之间只需要登录一次即可。

    一般来说单点登录实现原理为,首次访问一个站点时会被引导至登录页,用户登录验证通过,浏览器会存储一个关键key(一般采用cookie),用户访问其他系统时会带着这个key,服务器系统发现具有key标志后,会对其进行验证,如果通过便不需要再次登录了。

    这里的关键为二,第一是key,其二为系统统一的认证机制,这当中key会有一系列规则保证登录的安全性,而统一的认证机制是单点登录的前提,key是由他提供出去,每次用户由以这个给出的key来这里验证,判断其有效性,这里的统一的验证平台便是passport,他负责着制作通行令牌,并且对其进行验证。

    这里举个例子来说(有误请您指出):

    子系统A统一到passport服务器鉴权,并且在passport处获取cookie,并将token加入url,跳转回子系统A
    跳回子系统A后,使用token再次去passport验证(这里直接走webservice服务验证,不再跳转),验证通过便在A系统服务器生成session,以后访问子系统A,在有效时间内不到passport验证
    进入子系统B,跳到passport鉴权,发现passport cookie已经存在(token),便直接跳回子系统B,url带有token值,使用token去验证流程与A一致

    实现原理

    以腾讯百度等公司产品来说,他们一般会有一个统一的域,比如:

    baidu.com => tieba.baidu.com、music.baidu.com、baike.baidu.com......

    qq.com => feiji.qq.com、cd.qq.com、music.qq.com

    这类共享一个主域,但是这类网站往往还会提供给第三方使用,比如腾讯会给tencent.com与jd.com使用,这个时候一些实现细节又不一样,但有一点是确定的:

    单点登录要求共享一套账号体系,至少保存各个子系统的公共信息

    用户登录态,我们存于session中,这个时候第一类站点的单点登录便比较简单,以百度为例,直接设置baike.baidu.com与tieba.baidu.com的session为cookie domain为baidu.com便可解决问题。

    第二种情况比较普遍,便是music.qq.com与jd.com要实现单点登录,这个便完全跨域了,SSO的做法是将登录态保存至公用域,一般是passport.xx.com,比如百度为passport.baidu.com。

    这个时候tieba.baidu.com或者music.baidu.com的授权检查与退出操作全部由SSO(passport.baidu.com)来进行

    简单实现

    单点登录的调用表现一般有两种:

    ① 跳转

    ② 弹出层回调

    当用户在子系统未登录时,便会携带相关参数,比如tieba.baidu.com去到SSO(passport.baidu.com)进行登录

    登录成功SSO会生成ticket key并附加给源网址跳转回去,这个时候SSO上已经有用户的登录状态了,但是各个子系统仍然没有登录态,所以根据这个ticket设置各个子系统独立的登录态是需要的。

    当在SSO验证结束后,会回到子系统,子系统会根据得到的ticket(SSO为此次登录生成的用户基本信息加密串)获取用户的基本信息,从而在本站设置登录态。

    这里使用代码做一个说明,有2年没有写服务器端代码了,最后选来选去使用了node实现,才发现自己对node也很不熟悉啊!!!

    passport的实现

    首先我们看看鉴权代码的实现

    复制代码
     1 var path = require('path');
     2 var express = require('express');
     3 var router = express.Router();
     4 //偷懒,将token数据写入文件
     5 var fs = require('fs');
     6 
     7 /* GET home page. */
     8 router.get('/', function (req, res, next) {
     9   if (!req.query.from) {
    10     res.render('index', {
    11       title: '统一登录passport'
    12     });
    13     return;
    14   }
    15   var from = req.query.from;
    16   var token = null;
    17   var cookieObj = {};
    18   var token_path = path.resolve() + '/token_user.json';
    19   req.headers.cookie && req.headers.cookie.split(';').forEach(function (Cookie) {
    20     var parts = Cookie.split('=');
    21     cookieObj[parts[0].trim()] = (parts[1] || '').trim();
    22   });
    23   token = cookieObj.token;
    24   //如果url带有token,则表明已经在passport鉴权过
    25   if (token) {
    26     //存在token,则在内存中寻找之前与用户的映射关系
    27     //异步的
    28     fs.readFile(token_path, 'utf8', function (err, data) {
    29       if (err) throw err;
    30       if (!data) data = '{}';
    31       data = JSON.parse(data);
    32       //如果存在标志,则验证通过
    33       if (data[token]) {
    34         res.redirect('http://' + from + '?token=' + token);
    35         return;
    36       }
    37       //如果不存在便引导至登录页重新登录
    38       res.redirect('/');
    39     });
    40   } else {
    41     res.render('index', {
    42       title: '统一登录passport'
    43     });
    44   }
    45 });
    46 
    47 router.post('/', function (req, res, next) {
    48   if (!req.query.from) return;
    49   var name = req.body.name;
    50   var pwd = req.body.password;
    51   var from = req.query.from;
    52   var token = new Date().getTime() + '_';
    53   var cookieObj = {};
    54   var token_path = path.resolve() + '/token_user.json';
    55   //简单验证
    56   if (name === pwd) {
    57     req.flash('success', '登录成功');
    58     //passport生成用户凭证,并且生成令牌入cookie返回给子系统
    59     token = token + name;
    60     res.setHeader("Set-Cookie", ['token=' + token]);
    61     //持久化,将token与用户的映射写入文件
    62     fs.readFile(token_path, 'utf8', function (err, data) {
    63       if (err) throw err;
    64       if (!data) data = '{}';
    65       data = JSON.parse(data);
    66       //以token为key
    67       data[token] = name;
    68       //存回去
    69       fs.writeFile(token_path, JSON.stringify(data), function (err) {
    70         if (err) throw err;
    71       });
    72     });
    73     res.redirect('http://' + from + '?token=' + token);
    74   } else {
    75     console.log('登录失败');
    76   }
    77 });
    78 module.exports = router;
    复制代码

    子系统A的实现

    复制代码
     1 var express = require('express');
     2 var router = express.Router();
     3 var request = require('request');
     4 
     5 /* GET home page. */
     6 router.get('/', function (req, res, next) {
     7   var token = req.query.token;
     8   var userid = null;
     9   //如果本站已经存在凭证,便不需要去passport鉴权
    10   if (req.session.user) {
    11     res.render('index', {
    12       title: '子产品-A-' + req.session.user,
    13       user: req.session.user
    14     });
    15     return;
    16   }
    17   //如果没有本站信息,又没有token,便去passport登录鉴权
    18   if (!token) {
    19     res.redirect('http://passport.com?from=a.com');
    20     return;
    21   }
    22   //存在token的情况下,去passport主站检查该token对应用户是否存在,
    23   //存在并返回对应userid
    24   //这段代码是大坑!!!设置的代理request不起效,调了3小时
    25   request(
    26     'http://127.0.0.1:3000/check_token?token=' + token + '&t=' + new Date().getTime(),
    27     function (error, response, data) {
    28       if (!error && response.statusCode == 200) {
    29         data = JSON.parse(data);
    30         if (data.code == 0) {
    31           //这里userid需要通过一种算法由passport获取,
    32           //这里图方便直接操作token
    33           //因为token很容易伪造,所以需要去主战验证token的有效性,
    34           //一般通过webservers 这里验证就简单验证即可......
    35           userid = data.userid;
    36           //有问题就继续登录
    37           if (!userid) {
    38             res.redirect('http://passport.com?from=a.com');
    39             return;
    40           }
    41           //取得userid后,系统便认为有权限去数据库根据用户id获取用户信息
    42           //根据userid操作数据库流程省略......
    43           // req.session.user = userid;
    44           res.render('index', {
    45             title: '子产品-A-' + userid,
    46             user: userid
    47           });
    48           return;
    49         } else {
    50           //验证失败,跳转
    51           res.redirect('http://passport.com?from=a.com');
    52         }
    53       } else {
    54         res.redirect('http://passport.com?from=a.com');
    55         return;
    56       }
    57     });
    58 });
    59 module.exports = router;
    复制代码

    passport鉴权程序模拟

    复制代码
     1 var express = require('express');
     2 var router = express.Router();
     3 var path = require('path');
     4 var fs = require('fs'); 
     5 
     6 /* GET users listing. */
     7 router.get('/', function(req, res, next) {
     8   var token = req.query.token;
     9   var ret = {};
    10   var token_path = path.resolve() + '/token_user.json';
    11   ret.code = 1;//登录失败
    12   if(!token) {
    13     res.json(ret);
    14     return;
    15   }
    16   //如果传递的token与cookie不等也认为是鉴权失败
    17   // if(token != cookieObj['token']){
    18   //     res.json(ret);
    19   //   return;
    20   // }
    21   fs.readFile(token_path, 'utf8', function (err, data) {
    22         if (err) throw err;
    23         if(!data) data = '{}';
    24         data = JSON.parse(data);
    25         //如果存在标志,则验证通过,未考虑账号为0的情况
    26         if(data[token]) {
    27             ret.code = 0;//登录成功
    28             ret.userid = data[token];
    29         }
    30         res.json(ret);
    31     });
    32 });
    33 module.exports = router;
    复制代码

    简单测试

    测试之前需要设置host,这里继续采用fiddler神器帮助:

    这边直接用node跑了3个服务器:

    然后打开浏览器输入a.com,直接跳到了,登录页:

    简单登录后(账号密码输入一致即可):

    这个时候直接进入子系统B:

    直接便登录了,说明方案大体上是可行的

    结语

    首先,这里的程序很简陋,很多问题,也没有做统一退出的处理,今天主要目的是了解单点登录,后面有实际工作再求深入。

    这里附上源码,感兴趣的朋友看看吧:http://files.cnblogs.com/files/yexiaochai/web.rar,注意依赖包

  • 相关阅读:
    python note 30 断点续传
    python note 29 线程创建
    python note 28 socketserver
    python note 27 粘包
    python note 26 socket
    python note 25 约束
    Sed 用法
    python note 24 反射
    python note 23 组合
    python note 22 面向对象成员
  • 原文地址:https://www.cnblogs.com/apache-x/p/5454449.html
Copyright © 2020-2023  润新知