引子
昨天在网上看到一个帖子,帖子的内容大概是说领导要求一个苦B程序员实现一个单点登录的系统,将各个业务系统联系起来,但不能修改其他业务系统的源码。
其实,在企业信息化过程中,通常有多个应用系统,每个应用系统中,有独立用户管理模块,用来保存每个用户对应的账号,权限等信息,为了减少每个人登录系统时,记忆密码的麻烦,经常会用到单点登录功能。
我们公司用的就是CAS单点登录系统,我们抛开CAS这种成熟的单点登录系统,从头开始思考和设计一下单点登录如何实现。
需求简述
单点登录系统保存了用户的登录名和密码,上网用户在单点登录系统中认证成功后,就可以直接登录各个业务系统。
这个需求看似简单,但实际暗藏玄机:
1. 用户访问任何一个业务系统时,如果已经在单点登录服务器中认证成功,那么可以获取对应的权限,访问对应的界面。
2. 用户如果在其中任何一个业务系统中点击“注销”按钮后,那么不能继续访问其他业务系统,如果访问,必须重新登录
3. 用户访问任何一个业务系统时,如果尚未在单点登录服务器中认证成功,那么需要跳转到单点登录界面,输入用户名密码,校验成功后,再回到原来的访问界面
4. 用户关闭浏览器后,再次打开,不能继续访问其他业务系统
实现方案
根据上述需求,我们可以考虑出很多套实现方案,这些方案各有优缺点,根据各个方案的比较,选出实现最简单、功能最完善、性能最优化的方案,作为最终的实现方案。
我们知道用户点击业务系统中的各个连接,访问业务系统时,可能存在以下场景
场景1:用户尚未在单点登录系统中完成登录,此时单点登录系统没有当前用户的在线信息
场景2:用户已经在单点登录系统中完成登录,但尚未在当前业务系统中完成登录,当前业务系统中没有此用户的在线信息
场景3:用户已经在单点登录系统中完成登录,并且也在当前业务系统中完成登录
场景4:用户已经在单点登录系统中注销,但在当前业务系统中尚未注销
对于场景1,此时业务系统应该拦截用户的访问请求,并且将用户重定向到单点登录系统中,当用户在单点登录系统中完成登录后,再在当前业务系统中执行用户登录的操作,再重定向到用户上次访问的界面,让用户能够正常访问业务系统
对于场景2,此时业务系统应该拦截用户的访问请求,并且与单点登录系统通信,获取当前用户的在线状态后,在当前业务系统中执行登录操作,再向用户返回上次请求的结果界面,让用户能够正确访问业务系统
对于场景3,此时业务系统应该拦截用户的访问请求,并且与单点登录系统通信,校验用户是否在线,再向用户返回上次请求的结果界面,让用户能够正确访问业务系统
对于场景4,此时业务系统应该拦截用户的访问请求,并且与单点登录系统通信,校验用户是否在线,因为此时用户已下线,所以在当前业务系统中完成注销操作,并且将用户跳转到单点登录的登录界面,后续处理与情况1一致。
方案一
1. 用户访问业务系统时,业务系统可以根据HTTP请求的源IP,去单点登录系统中查询
1). 如果此IP对应的用户尚未认证,跳转到单点登录系统的登录界面,要求输入用户名和密码进行认证
2). 如果此IP对应的用户已经认证,业务系统认为此用户登录成功,允许其继续访问业务系统
2. 在单点登录系统的登录界面或后台session中,需要保存用户上次访问的URL,以便于用户认证成功后,能够再次回到上次访问的界面
3. 用户在单点登录系统的登录界面输入用户名密码登录成功后,单点登录系统记录此用户的身份以及对应的IP地址,再将浏览器重定向到上次访问的URL中,这样就回到了步骤1,此时用户已经认证成功,可以访问业务系统。
4. 用户在任意业务系统中单击注销按钮时,业务系统完成系统自身的注销操作后,将界面重定向到单点登录系统的注销URL中,并自动在单点登录系统中注销用户信息
1. 只要浏览器支持基本的重定向功能,就可以按照本方案实现
1. 要实现上述需求,需要修改业务系统的代码,对于.net和java编写的业务系统,需要两套不同的代码
1. 根据用户的IP地址进行用户身份的判定,会带来以下安全问题:
1). 如果用户IP修改后,需要重新认证才能继续访问业务系统
2). 如果用户恶意仿冒领导的IP地址,那么可以越权访问自己没有权限的业务系统
3). 在公共的PC中,前一个用户关闭浏览器,没有点击注销按钮,那么后续使用这台PC的所有人,都可以直接使用前一个人的账号访问业务系统
每次访问业务系统的任何一个URL,都需要与单点登录系统联动,如果用户量很大,单点登录系统会成为瓶颈
方案二
1. 用户使用单点登录系统的登录界面,输入用户名和密码登录成功后,单点登录系统为用户浏览器安装一个cookie,cookie的值是一个全局唯一的字符串 (下文称为ticket),理论上这个唯一值永远不能重复,用来标识用户的一次成功的登录请求。同一个用户,在同一个时刻登录两次,得到的ticket值 应该完全不同。
2. 用户访问业务系统时,如果当前用户尚未在业务系统中登录,就将界面重定向到单点登录系统中,这时访问的URL前缀是单点登录系统的前缀
1).如果用户已经在单点登录系统中完成登录,那么此时用户访问单点登录URL时,就会带上步骤1中的ticket,单点登 录系统识别出此ticket,知道当前用户已经认证过,将用户界面再次跳转到业务系统中,并且携带上述ticket,业务系统也将此ticket安装到 cookie中,后续对于此业务系统的所有访问,都直接根据ticket去单点登录系统中查询用户是否在线就可以了
2).如果用户尚未在单点登录系统中完成登录,那么此时用户访问单点登录URL时,无法携带上步骤1中的ticket,单点 登录系统为展示登录界面,用户输入用户名和密码登录成功后,将用户界面再次跳转到业务系统中,并且携带上述ticket,业务系统也将此ticket安装 到cookie中,后续对于此业务系统的所有访问,都直接根据ticket去单点登录系统中查询用户是否在线就可以了
3. 用户访问业务系统时,如果当前用户已经在业务系统中登录,那么访问业务系统的cookie中,会携带单点登录ticket,业务系统根据此ticket,去单点登录系统中查询用户是否已经在线,如果已经在线,就允许继续访问,否则就执行注销操作
4. 用户在任意一个业务系统中执行注销操作时,业务系统在拦截注销操作,并且与单点登录系统联动,在单点登录系统中完成注销后,再跳转回业务系统的注销界面
1. 单点登录系统的整套拦截和转发流程,可以封装成为公共组件,只需少许修改业务系统代码
2. 所有业务系统都可以使用上述方案增加与单点登录系统的联动功能
上述单点登录功能,依赖浏览器的cookie功能,如果浏览器不支持cookie,将无法使用
1. 用户恶意仿冒他人IP,也无法获取对应业务系统的访问权限,安全性比方案一高
2. 恶意用户拦截HTTP请求,获取cookie中进行仿冒,同样可以获取他人对于业务系统的访问权限
当业务系统很多、上网用户很多时,每次访问业务系统的任何一个链接,都需要与单点登录系统联动,查询用户是否在线,单点登录系统可能成为瓶颈
方案比较
上述方案,都需要对于现有系统进行一些修改,但方案二的安全性相对较高
方案二中提到的安全性问题,可以通过定期更新ticket值,或每次访问都生成不同的ticket值来进行规避。
上述方案,因为涉及对于单点登录系统的大量访问,所以会使得单点登录系统成为瓶颈,可以采用如下方案在安全性不降低很多的情况下规避性能问题:
方案一:业务系统记录上次与单点登录系统联动,获取用户状态的时间,并且n分钟内,所有用户对于业务系统的情况,都不和单点登录系统联动
方案二:业务系统和单点登录系统之间采用性能更高的网络协议,例如UDP协议进行在线状态的交互,按照现有经验,因为UDP报文头部较小,报文有效内容比例大,同时报文长度短,比现有的HTTP协议性能可以提高1~2个数量级,每秒支撑1000次查询请求,是没有问题的。
总结
上述方案二,是借鉴CAS单点登录系统的实现进行描述的。
对于第一章节提到的那种对于现有业务系统不进行任何修改的方案,个人认为没有太好的实现方法,可能可以通过简单修改业务系统的asp,php或jsp代码实现,但是一点不修改,目前没有太好的方案可以做到。