Nodejs----单点登录

---------------------------------------------------------------------------------------------单点登陆原理-----------------------------------------------------------------------------------------------------------------------------

        1:http无状态协议:
                web采用客户端-服务端架构,http做为通信协议,浏览器的每一次请求,服务器会独自处理,
                不与之间之后的请求产生关联。
                三次请求/响应之间没有任何关系:
                        浏览器                      客户端
                                ------------------------
                           |                                            | 
                           -------第(1)次请求-------->
                           |                                            |
                           <------第(1)次响应--------
                           |                                            |               
                           |                                            |
                           |                                            |
                           -------第(2)次请求-------->
                           |                                            |
                           <------第(3)次响应---------
                           |                                            |
                           |                                            |
                           |                                            |
                           -------第(2)次请求-------->
                           |                                            |
                           <------第(3)次响应---------
                           |                                            |
                           |                                            |
                                ------------------------
                        但这也同时意味着,任何用户都能通过浏览器访问服务器资源,如果想保护服务器的某些资源,
                        必须限制浏览器请求;要限制浏览器请求,必须鉴别浏览器请求,响应合法请求,忽略非法请求;
                        要鉴别浏览器请求,必须清楚浏览器请求状态。
                        既然http协议无状态,那就让服务器和浏览器共同维护一个状态吧!这就是会话机制
                          
        2: 会话机制:
                浏览器第一次请求服务器,服务器创建一个会话,并将会话的id作为响应的一部分发送给浏览器,
                浏览器存储会话id,并在后续第二次和第三次请求中带上会话id,
                服务器取得请求中的会话id就知道是不是同一个用户了,这个过程用下图说明,后续请求与第一次请求产生了关联:
                
                        浏览器                      客户端
                                ------------------------
                           |                                            | 
                           -------第(1)次请求-------->---------------[创建会话]
                           |                                            |                         |
                           |                        |<------------------------|
                           |                        |
                           |                        |
                           <------第(1)次响应------------->(会话id)
                           |                        |
                           |-------------|                      |               
                           | 保存会话(id)|          |
                           |     <-------|          |
                           |                                            |
                           -------第(2)次请求-------------->(会话id)
                           |                                            |
                           <------第(3)次响应---------
                           |                                            |
                           |                                            |
                           |                                            |
                           -------第(2)次请求--------------->(会话id)
                           |                                            |
                           <------第(3)次响应---------
                           |                                            |
                           |                                            |
                                ------------------------
                        
                        服务端在内存中保存会话对象,浏览器怎么保存会话id:
                                请求参数:
                                        将会话id作为一个请求参数,服务器接收请求自然能解析参数获得会话信息,并借此来判断是否来自同一
                                        会话,很明显,这种方式不靠谱。
                                cookie:
                                        那就浏览器自己来维护这个会话id,每次发送http请求时浏览器自动发送
                                        会话id,cookie机制正好可以来处理这件事。cookie时浏览器用来存储少量
                                        数据的一种机制,数据以Key:value的形式存储,浏览器请求时自动附带cookie信息。
                                        
                        tomcat会话机制当然也实现了cookie,访问tomcat服务器时,
                                浏览器中可以看到一个名为“JSESSIONID”的cookie,这就是tomcat会话机制维护的会话id.
                                
                                浏览器                      客户端
                                        ------------------------------
                                   |                                                   | 
                                   -------第(1)次请求--------------->---------------[创建会话]
                                   |                                                   |                         |
                                   |                               |<------------------------|
                                   |                               |
                                   |                               |
                                   <------第(1)次响应---------------(cookie:JSsessionid)
                                   |                               |
                                   |----------------------|                |            
                                   | 设置cookie(jssessionid)       |
                                   |     <----------------|        |
                                   |                                                   |
                                   -------第(2)次请求--------------->(cookie:JSsessionid)
                                   |                                                   |
                                   <------第(3)次响应---------------
                                   |                                                   |
                                   |                                                   |
                                   |                                                   |
                                   -------第(2)次请求-------------->(cookie:JSsessionid)
                                   |                                                   |
                                   <------第(3)次响应---------------
                                   |                                                   |
                                   |                                                   |
                                        -------------------------------

        3: 单点登录:
                什么是单点登录:
                        单点登录全称Single Sign On(以下简称SSO),
                        是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,
                        包括单点登录与单点注销两部分
                        
                登录:
                        相比于单系统登录,sso需要一个独立的认证中心,
                        只有认证中心能接受用户的用户名密码等安全信息,
                        其他系统不提供登录入口,只接受认证中心的间接授权。
                        间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,
                        在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,
                        可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。
                        
                        图示:
                                                浏览器             系统1               系统2          sso认证中心 
                                                   *-----------------*--------------------*-----------------*-
                                                   |                 |                    |                 |
                                                   |                 |                    |                 |
                                                   |                 |                    |                 |
                                                   ---访问(系统1)------>                  |                                  |
                                                   |                 |                    |                 |
                                                   |                 |-----------------   |                 |
                                                   |                 |                |   |                 |
                                                   |                 <----验证未登陆---   |                 |
                                                   |                 |                    |                 |
                                                   |                 |                    |                 |
                                                   |                 |----------跳转(系统1的地址)------------>
                                                   |                 |                    |                 |------------------
                                                   |                 |                    |                 |                 |
                                                   |                 |                    |                 <----验证未登陆-----
                                                   |                 |                    |                 |
                                                  <-----------登陆页面(系统1的地址)--------------------------
                                                   |                                      |                 |
                                                   ------------登陆(usrname,password,系统1的地址)----------->
                                                   |                  |                    |                |-------------------
                                                   |                  |                    |                |                  |
                                                   |                  |                    |               <---验证成功---------
                                                   |                  |                    |                |
                                                   |                  |                    |                |-------------------
                                                   |                  |                    |                |                  |
                                                   |                  |                    |               <---创建全局会话-----
                                                   |                  |                    |                |
                                                   |                  |                    |                |-------------------
                                                   |                  |                    |                |                  |
                                                   |                  |                    |               <---创建授权令牌-----
                                                   |                 <----------跳转(令牌)-------------------
                                                   |                  |                    |                |
                                                   |                  |                    |                |
                                                   |                  -----------校验(令牌)------------------>
                                                   |                  |                    |                |
                                                   |                  |                    |                |-------------------
                                                   |                  |                    |                |                  |
                                                   |                  |                    |               <-----令牌有效-------
                                                   |                              |                    |                |
                                                   |                  |                    |                |-------------------
                                                   |                  |                    |                |                  |
                                                   |                  |                    |               <-----注册系统--------
                                                   |                  |                    |                |
                                                   |                  |                    |                |
                                                   |                              <---------令牌有效----------------------
                                                   |                  |                    |                |
                                                   |                  |---------------------------          |
                                                   |                  |                          |----令牌  |
                                                   |                  |<---创建局部会话信息-------          |
                                                   |                  |                    |                |  
                                                   |<--受保护资源-----|                    |                              |
                                                   |
                                                   |
                                                   |
                                                   |-----------------访问----------------->|                |
                                                   |                                       |                |              
                                                   |                                       | --验证未登陆   |   
                                                   |                                       |            |   |
                                                   |                                       |<------------   |
                                                   |                                       |                |
                                                   |                                       |-------调转----->
                                                   |                                       |                |-----------------
                                                   |                                       |                |                |
                                                   |                                       |                |<---验证已登陆---                               
                                                   |                                       |                |
                                                   |                                       |                |
                                                   |                                       |                |
                                                   |                                       |--校验令牌------->
                                                   |                                       |                |
                                                   |                                       |                |------------------
                                                   |                                       |                |                             |
                                                   |                                       |                |<令牌有效---------
                                                   |                                       |                |
                                                   |                                       |                |------------------
                                                   |                                       |<---------------|                 |
                                                   |                                       |                |<--注册系统-------
                                                   |                                       |                |
                                                   |                                       |                |
                                                   |                                       |                |
                                                   |                                       |<-令牌有效------|
                                                   |                                       |                |
                                                   |                                       |                |
                                                   |                                       |                |
                                                   |                                       |------------
                                                   |                                       |           |
                                                   |                                       |<-----创建局部会话(令牌)
                                                   |                                       |
                                                   |                                       |
                                                   |                                       |
                                                   |<-----------受保护资源-----------------|                                 
                                   
                        解释:
                                1:用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
                                2:sso认证中心发现用户未登录,将用户引导至登录页面
                                3:用户输入用户名密码提交登录申请
                                4:sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌
                                5:sso认证中心带着令牌跳转会最初的请求地址(系统1)
                                6:系统1拿到令牌,去sso认证中心校验令牌是否有效
                                7:sso认证中心校验令牌,返回有效,注册系统1
                                8:系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
                                9:用户访问系统2的受保护资源
                                10:系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
                                12:sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌
                                13:系统2拿到令牌,去sso认证中心校验令牌是否有效
                                14:sso认证中心校验令牌,返回有效,注册系统2
                                15:系统2使用该令牌创建与用户的局部会话,返回受保护资源
                                16:用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,
                                   局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系
                                17:局部会话存在,全局会话一定存在
                                18:全局会话存在,局部会话不一定存在
                                19:全局会话销毁,局部会话必须销毁

                注销:
                        单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁。
                        
                        图示:     
                                        浏览器                  系统1               系统2                  sos认证中心
                                          |                       |                   |                         |
                                          |                       |                   |                         |
                                           ---------------------------------------------------------------------
                                          |                       |                   |                         |
                                          |                                               |                               |                                                     |
                                          |                       |                   |                         |
                                          ---销毁请求(id)--------->
                                          |                       |                   |                         |
                                          |                       |-----------        |                         | 
                                          |                       |          |        |                         |
                                          |                       |<-取出令牌(会话id)
                                          |                       |                                             |
                                          |                       |                    |                        |
                                                                                          ---------------------------------------------->
                                          |                       |                    |                        |
                                          |                       |                    |                        |----------------
                                          |                       |                    |                        |                               |
                                          |                       |                    |                       |<--校验令牌有效(令牌)        
                                          |                       |                    |                        |                 
                                          |                       |                    |                        |
                                          |                       |                    |                        |-----------------                                                                                                                                                                              
                                          |                       |                    |                        |                |
                                          |                       |                    |                        |<--销毁全局会话--(令牌)
                                          |                       |                    |                        |
                                          |                       |                    |                        |-----------------
                                          |                       |                    |                        |                |
                                          |                       |                    |                        |<-取出注册系统---(令牌)
                                          |                       |                    |                        |
                                          |                       |                    |                        |
                                          |                       |                    |<--销毁局部会话令牌-----|
                                          |                       |                    |                        |
                                          |                       |<---销毁全局会话令牌-------------------------|
                                          |                       |                    |                        |
                                          |<----------------------------登陆页面--------------------------------|
                                  
                        解释:
                                1:sso认证中心一直监听全局会话的状态,一旦全局会话销毁,监听器将通知所有注册系统执行注销操作
                                2:用户向系统1发起注销请求
                                3:系统1根据用户与系统1建立的会话id拿到令牌,向sso认证中心发起注销请求
                                4:sso认证中心校验令牌有效,销毁全局会话,同时取出所有用此令牌注册的系统地址
                                5:sso认证中心向所有注册系统发起注销请求
                                6:各注册系统接收sso认证中心的注销请求,销毁局部会话
                                7:sso认证中心引导用户至登录页面

        4:部署图:
                单点登录涉及sso认证中心与众子系统,子系统与sso认证中心需要通信以交换令牌、校验令牌及发起注销请求,
                因而子系统必须集成sso的客户端,sso认证中心则是sso服务端,
                整个单点登录过程实质是sso客户端与服务端通信的过程。
                
                 系统1-------httpClient---------------sso认证中心---------------httpClient---------系统2
                   |                                       |                                         |
                   |                                       |                                         |
                   |                                       |                                         |
                   |                                       |                                         |
                   -------------------------------------------------------------------------------------
                                                         防火墙
           -------------------------------------------------------------------------------------
                        -                                    |
                                -                                    |
                                -                                    |
                                -                                    |
                        点击登陆应用,                           |
                        用户访问部署了单点登录的多系统应用群,   |
                        一次登录,到处使用                       |
                                                             |
                                                                                                         |
                                                                                                         |
                                                                                                --------------
                                                  用户电脑                                                                                          
                                
        5:实现:
                1:拦截子系统未登录用户请求,跳转至sso认证中心
                2:接收并存储sso认证中心发送的令牌
                3:与sso-server通信,校验令牌的有效性
                4:建立局部会话
                5:拦截用户注销请求,向sso认证中心发送注销请求
                6:接收sso认证中心发出的注销请求,销毁局部会话sso-server
                7:验证用户的登录信息
                8:创建全局会话
                9:创建授权令牌
                10:与sso-client通信发送令牌
                11:校验sso-client令牌有效性
                12:系统注册
                13:接收sso-client注销请求,注销所有会话
                第一步:sos-client拦截未登录请求。
                第二步:sos-aerver拦截未登录入请求。
                第三步:sso-server验证用户登录信息。
                第四步:sso-server创建授权令牌。
                第五步:sso-client取的令牌并校验。
                第六步:sso-server接收并处理校验令牌请求。
                第七步:sso-client校验令牌成功创建局部会话。
                第八步:注销过程。
                
    6:代码:
                        /**
                         * Created by Mloong on 2018/7/30
                         * 实现单点登录
                         */
                        var express = require('express');
                        var app = express();
                        var bodyparser = require('body-parser');
                        var crypto = require('crypto');
                        var session = require('express-session');
                        var cookie = require('cookie-parser');
                        var path = require('path');
                        var multipart = require('connect-multiparty');
                        var multipartMiddleware = multipart();
                        app.use(bodyparser.json());
                        var mdb;

                        /**
                         * session,cookie中间件。
                         */
                        app.use(cookie());
                        app.use(session({
                            secret: 'secret', // 对session id 相关的cookie 进行签名
                            resave: true,
                            saveUninitialized: false, // 是否保存未初始化的会话
                            cookie: {
                                maxAge: 1000 * 60 * 3 // 设置 session 的有效时间,单位毫秒
                            }
                        }));
                        //app.set('tem', __dirname); //设置模板的目录
                        //app.set('view engine', 'html'); // 设置解析模板文件类型:这里为html文件
                        //app.engine('html', require('ejs').__express); // 使用ejs引擎解析html文件中ejs语法
                        //app.use(bodyparser.json()); // 使用bodyparder中间件,
                        //app.use(bodyparser.urlencoded({ extended: true }));


                        /**
                         * 连接mongodb
                         */
                        var MongoClient = require('mongodb').MongoClient;
                        var url = "mongodb://localhost:27017/runoob";


                        /**
                         * 生成令牌
                         * 生成token
                         * @return {string} return 返回值
                         * */
                        function genToken()
                        {
                            var buf = crypto.randomBytes(12);
                            var token = buf.toString('hex');
                            return token;
                        }


                        /**
                         * 请求数据库
                         */
                        MongoClient.connect(url, function (err, db)
                        {
                            if (err) throw err;
                            var dbo = db.db("runoob");
                            mdb = dbo;
                        });

                        /**
                         * 注册
                         */
                        app.get('/register', function (req, res)
                        {
                            res.sendFile(path.join(__dirname, './public/templates', 'register.html'));
                        });

                        app.post('/register', multipartMiddleware, function (req, res)
                            {
                                var username = req.body.user;
                                var password = req.body.pwd;
                                console.log(username);
                                mdb.collection('user').findOne({username: username}, function (err, result)
                                {
                                    if (err) throw err;
                                    if (result)
                                    {
                                        res.json({
                                            ret_code: 1,
                                            ret_msg: '用户名已存在请更换用户名!'
                                        });
                                    }
                                    else
                                    {
                                        mdb.collection('user').insertOne({usernaem: username, password: password}, function (err, result)
                                        {
                                            if (err) throw err;
                                            res.redirect('/login');
                                        });

                                    }
                                });
                            }
                        );

                        /**
                         * 登录
                         */
                        app.get('/login', function (req, res)
                        {
                            res.sendFile(path.join(__dirname, './public/templates', 'login.html'));
                        });
                        app.post('/login', function (req, res)
                        {
                            var username = req.body.user;
                            var password = req.body.pwd;
                            mdb.collection("user").findOne({username: username, password: password}, function (err, result)
                            {
                                if (err) throw err;
                                if (result)
                                {
                                    var ticket = genToken();
                                    mdb.collection('token').insertOne({ticket: ticket}, function (err, Lresult) {});
                                    req.session.ticket = ticket;
                                    res.cookie.ticket = ticket;
                                    res.redirect('/index');
                                } else
                                {
                                    res.json({
                                        ret_code: 1,
                                        ret_msg: '用户名或密码错误!'
                                    });
                                }
                            });
                        });

                        /**
                         * 认证中心
                         */
                        app.get('/authentication', function (req, res)
                        {
                            if (req.session.ticket)
                            {
                                console.log("进入认证");
                                var url = req.query.callback;
                                var token = req.session.ticket;
                                url = console.log(url + "?token=" + token);
                                res.redirect(url);
                            }
                            else
                            {
                                res.redirect('/login');
                            }
                        });

                        /**
                         * 首页
                         */
                        app.get('/index', function (req, res)
                        {
                            if (req.session.ticket)
                            {
                                res.sendFile(path.join(__dirname, './public/templates', 'index.html'));
                            }
                            else
                            {
                                res.redirect('/login');
                            }
                        });

                        /**
                         * 注销
                         */
                        app.post('/cancellation', function (req, res)
                        {
                            var token = req.session.ticket;
                            delete req.session.ticket;
                            mdb.collection('user').removeOne({ticket: token}, function (ree, result)
                            {
                                if (err) throw err;
                                res.redirect('/login');

                            });

                        });
                        var server = app.listen(8881, function ()
                        {
                            var host = server.address().address;
                            var port = server.address().port;
                            console.log("访问地址为 http://%s:%s", host, port);
                        });