什么是单点登录?如何进行登录和权限验证?

单点登陆原理及实现
随着业务发展,公司业务会不断壮大,每位业务就会存在用户登入和权限验证,不可能要求用户每位业务网站都登入一次,这个时侯,就须要单点登陆功能。下边将先介绍基本概念,之后以百度()为例进行讲解,最后用一个小反例讲解怎样实现(后台用node)。本文是假定读者对http合同等有基本了解。
#
哪些是单点登陆? #
单点登陆即用户在公司的某个网站登陆后,访问该公司相关的网站都是已登入状态,无需再进行登陆操作。比如我们登陆百度首页()后,访问百度晓得(),就早已登陆,无需再度登陆该站点,这就是单点登陆。这个功能很重要,以阿里巴巴来说,业务非常庞大,假如每位业务网站都要重新登陆,即便用户要动怒。
#
要实现单点登陆,就要验证该用户已登陆,问题就回归到用户的登入验证问题,怎么来确保该用户已登入。 #
用户登入和权限验证 #
先来看下服务是怎样进行登陆和权限验证的。我们登陆一个网页,进行操作,多个http恳求如下所示: #
整个登陆、权限验证、退出操作可以分为下边几个步骤:
#
1、用户输入用户名和密码,顾客端将用户名和加密后的密码发送至服务端。 #
2、服务端成功验证后,会生成一个加密的会话id,把装入id列表,同时,服务器会返回恳求,并在返回头中设置,让id储存在中。 #
3、用户发送其它恳求(恳求会携带中的id)至服务端如何实现单点登录,比如获取历史操作记录,查询顾客帐单等等。 #
4、服务端验证id的有效性,有效则返回正确信息。
#
5、用户发送退出登入操作。 #
6、服务端验证id,验证成功则从id列表中删掉该id,或把该id置为过期。
7、用户再度发出查询帐单恳求。
#
8、服务端验证id,发觉该id不存在或已过期,则提示用户重新登入。
从上述过程可以发觉,用户的权限验证,就是会话id的生成和校准,只要多个站点可以共享这个id,既可以完成单点登陆为何如此说呢?诱因如下: #
假定公司有两个站点,和,根据如今的构架设计,会把公共的业务模块具象下来,作为独立的服务。顾客信息作为一个公共功能,自然是独立的。构架设计图如下所示:
#
和有独立的服务器,但向顾客信息系统等公共模块是独立的,即顾客端恳求信息是,两个服务总统会向顾客信息管理系统进行顾客权限验证,通过验证才继续操作,返回顾客须要的信息。
之前我们早已的出推论,用户的权限验证,就是会话id的验证。假如两个站点的会话id共享,只需登入一次,两个站点的权限验证都可以完成。即单点登陆的关键在于两个站点怎样实现会话id()的共享,下边,我们以百度为例,剖析她们怎么实现共享的。 #
实例剖析
以百度为例,我们登陆百度首页()后,步入百度文库()、百度晓得()都是已登入状态,我们用调试工具分别看见几个站点的,发觉有部份是相同的。 #
实际上,为一级域名,其它都是二级域名,可以在二级域名间共享,只需对的进行设置即可。 #
前面这些情况比较可以共享,假如大家公司的各个业务站点一级域名一致,可以使用该方式实现单点登陆。并且,当站点之前不是同域,以及域名也不同时,是难以共享的。这是浏览器的同源策略导致的,目的是保护用户信息安全,避免不法分子盗取顾客信息,伪装用户进行操作。
#
我们以百度(),()两个站点为例进行测试,瞧瞧她们是怎样实现单点登陆的。实验步骤如下: #
1、清除两个站点的和登陆信息,使她们都处于非登入状态。
2、打开百度首页()并登陆,查看用户信息。
#
3、打开()站点,查看用户信息。 #
4、比对用户信息,发觉用户信息一致。即两者实现了单点登陆,只要登陆百度,即可登入网站。
我们查看两者,发觉二者中有多个值相同(比如都存在BDUSS这个值,且值相同),可以推测她们可能共用了一套用户信息系统,起码用户登入信息是有共享的。 #
在登陆是,我用(抓包工具,可自行百度)进行抓包,瞧瞧她们究竟进行了哪些操作?百度的登陆过程,抓包如下 #
通过剖析,我在图中用画了几道红线,开始第一个是百度帐号的登入验证,之后返回了一个加密的用户身分惟一标示(bdu),之后向站点发起恳求,该恳求携带用户身分标示(bdu),验证该标示后,返回会话id,并存在的的域中。如右图所示: #
如图所示,单点登陆过程可分为以下几个步骤:
1、用户李三打开网站,输入用户名密码,发送登陆恳求至服务器。 #
2、服务器向顾客信息管理中心发起恳求,验证李三用户名密码。 #
3、验证完后返回会话id和用户惟一标示(一串加密的字符串如何实现单点登录,用于跨域验证,暂时称它为sid)。
#
4、收到登陆成功信息和sid后,携带sid向服务器发起认证恳求。
#
5、服务器同样向顾客信息管理中心发起惟一标示认证。
6、客户信息管理中心认证成功返回会话id给服务器。
#
7、服务器返回会话id,会话id会储存在域的中。 #
8、后续访问网站,恳请还会携带会话id,后台领到id去认证。
这就是单点登陆的整个过程。 #
具体实现
#
下边用node模拟两个站点进行单点登陆模拟。 #
服务器1,有一个页面,两个插口。
#
登入页代码如下:
<html>
<head>
<meta charset="utf-8" />
<title>登录页面title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
div {
height: 30px;
width: 300px;
margin: 20px auto;
text-align: center
}
div > label {
display: inline-block;
width: 80px;
text-align: right;
padding-right: 15px;
box-sizing: border-box
}
div > input {
width: 200px
}
style>
head>
<body onload="addEvent()">
<div>
<label>userName:label>
<input type="text" id="user_name">
div>
<div>
<label>passwordlabel>
<input type="password" id="password">
div>
<div>
<input type="button" id="submit" value="logon" style="width: 100px">
div>
<script>
//添加按钮事件
function addEvent() {
let submit = document.getElementById('submit');
if (submit) {
submit.addEventListener('click', () => {
login();
});
}
}
//登录请求方法
function login() {
let userName = document.getElementById('user_name');
let password = document.getElementById('password');
$.ajax({
url: '/login',
method: 'GET',
data: {
userName: userName.value,
password: password.value
},
success: function(result) {
//使用用户唯一标识进行跨域认证
crossdomain(result.id);
location.href = location.protocol + '//' + location.host + '/success';
},
error: function(error) {
console.error('登录错误');
}
})
}
//跨域用户认证请求
function crossdomain(id) {
let iframe = document.createElement('iframe');
iframe.src = 'http://localhost:10000/crossdomain?id=' + id;
iframe.style.display = 'none';
document.body.appendChild(iframe);
}
script>
<script src="./jquery.js">script>
body>
html>
#
服务端代码如下:
#
const express = require('express');
const app = express();
const cookieParser = require('cookie-parser');
app.use(cookieParser());
//未登录访问带接口返回“无权限”,登录后返回“站点3000登录成功”
app.get('/success', (req, res) => {
let sessionId = req.cookies ? req.cookies.sessionId : '';
if (sessionId && sessionId === '123456') {
res.send('站点3000登录成功');
} else {
res.send('无权限');
}
});
//登录接口,验证成功返回会话id和用户登录唯一标识
app.get('/login', (req, res) => {
let name = req.query.userName;
let password = req.query.password;
if (name === 'user' && password === '123456') {
res.cookie('sessionId', '123456', { expires: new Date(Date.now() + 10 * 1000)});
res.send({
id: '123456789'
});
} else {
res.sendStatus(400);
}
});
//静态资源访问路径,路由中包含/static
app.use('/static', express.static('./resources'));
app.listen(3000, () => console.log('Example app listening on port 3000!'))
#
服务器2只有两个插口,代码如下:
#
const express = require('express');
const app = express();
const cookieParser = require('cookie-parser');
app.use(cookieParser());
//对于已认证用户,返回‘站点10000登录成功’,反之,返回‘无权限’
app.get('/getcontent', (req, res) => {
let sessionId = req.cookies ? req.cookies.sessionId : '';
if (sessionId && sessionId === '123456') {
res.send('站点10000登录成功');
} else {
res.send('无权限');
}
});
//认证用户接口,根据用户唯一标识认证用户是否登录
app.use('/crossdomain', (req, res) => {
let id = req.query.id;
if (id === '123456789') {
res.cookie('sessionId', '123456', { expires: new Date(Date.now() + 1000 * 1000)});
res.send('有权限');
} else {
res.send('无权限');
}
});
//静态资源访问路径,路由中包含/static
app.use('/static', express.static('./resources'));
app.listen(10000, () => console.log('Example app listening on port 10000!'))
#
#