单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
如下图,用户只需要认证一次,便可以在多个拥有访问权限的系统中访问。
单点登录原理
sso需要一个独立的认证中心,所有子系统都通过认证中心的登录入口进行登录,登录时带上自己的地址,子系统只接受认证中心的授权,授权通过令牌(token)实现,sso认证中心验证用户的用户名密码正确,创建全局会话和token,token作为参数发送给各个子系统,子系
单点登录实现方式
单点登录的实现方案,一般就包含:Cookie,Session,token,目前的大型网站都是采用分布式Session的方式。
基于Cookie+Redis的单点登录
最简单的单点登录实现方式,用cookie作为媒介存放用户凭证。 用户登录系统之后,会返回一个加密的cookie,当用户访问子应用的时候会带上这个cookie,授权以解密cookie并进行校验,校验通过后即可登录当前用户。
redis:在key:生成唯一随机值(ip、用户id等等),在value:用户数据
cookie:把redis里面生成key值放到cookie里面
下面对上图简要描述
1用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
2sso认证中心发现用户未登录,将用户引导至登录页面
3用户输入用户名密码提交登录申请
4sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌
5sso认证中心带着令牌跳转回最初的请求地址(系统1)
6系统1拿到令牌,去sso认证中心校验令牌是否有效
7sso认证中心校验令牌,返回有效,注册系统1
8系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
9用户访问系统2的受保护资源
10系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
11sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌
12系统2拿到令牌,去sso认证中心校验令牌是否有效
13sso认证中心校验令牌,返回有效,注册系统2
14系统2使用该令牌创建与用户的局部会话,返回受保护资源
用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系
1局部会话存在,全局会话一定存在
2全局会话存在,局部会话不一定存在
3全局会话销毁,局部会话必须销毁
Cookie的作用在于充当一个信息载体在Server端和Browser端进行信息传递,而Cookie一般是以域名为分割的,例如a.xxx.com与b.xxx.com的Cookie是不能互相访问的,但是子域名是可以访问上级域名的Cookie的。 即a.xxx.com和b.xxx.com是可以访问xxx.com下的Cookie的,于是就能将顶级域名的Cookie作为OpenId的载体。
验证步骤和上第二个方法非常相似:
在提供验证服务的站点里登录;
●将OpenId写入顶级域名Cookie里;
●访问子系统(Cookie里带有OpenId)
●子系统取出OpenId通过并向验证服务发送OpenId
●返回用户认证信息
●返回授权后的内容
但使用该方式把信任存储在客户端的Cookie中会有两个问题:
●Cookie不安全
●不能跨域实现免登
对于第一个问题,通过加密Cookie可以保证安全性,当然这是在源代码不泄露的前提下。如果Cookie的加密算法泄露,攻击者可以通过伪造Cookie伪造成特定用户身份。 对于问题二更是硬伤,所以才有了以下的分布式session方案。
分布式session方式实现单点登录
流程运行:
(1) 用户第一次登录时,将会话信息(用户Id和用户信息),比如以用户Id为Key,写入分布式Session;
(2) 用户再次登录时,获取分布式Session,是否有会话信息,如果没有则调到登录页;
(3) 一般采用Cache中间件实现,建议使用Redis,因此它有持久化功能,方便分布式Session宕机后,可以从持久化存储中加载会话信息;
(4) 存入会话时,可以设置会话保持的时间,比如15分钟,超过后自动超时;
结合Cache中间件,实现的分布式Session,可以很好的模拟Session会话。
使用token实现
1.在项目某个模块进行登录,登录之后,按照规则生成字符串,把登陆之后用户包含到生成字符串里面,把字符串返回
(1)可以把字符串通过cookie返回
(2)把字符串通过地址栏返回
2.再去访问项目其他模块,每次访问在地址栏带着生成的字符串token,在访问模块里面获取地址字符串,根据字符串获取用户信息。如果可以获取到就能登录,反之则未登录。
实现方式如下图: