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

  

原文地址:https://www.cnblogs.com/w-s-l123/p/9458872.html