单点登录实现方式
共享session
-
共享Session可谓是实现单点登录最直接、最简单的方式。将用户认证信息保存于Session中,即以Session内存储的值为用户凭证,这在单个站点内使用是很正常也很容易实现的,而在用户验证、用户信息管理与业务应用分离的场景下即会遇到单点登录的问题,在应用体系简单,子系统很少的情况下,可以考虑采用Session共享的方法来处理这个问题。
-
将Session存储于Redis上,然后将整个系统的全局Cookie Domain设置于顶级域名上,这样SessionID就能在各个子系统间共享。
跨域名情况无法处理,Session中所涉及的类型必须是子系统中共同拥有的,使用收到很多限制。
OpenId
将用户的身份标识信息简化为OpenId存放于客户端,当用户登录某个子系统时,将OpenId传送到服务端,服务端根据OpenId构造用户验证信息,多用于C/S与B/S相结合的系统,流程如下:
- 当用户第一次登录时,将用户名密码发送给验证服务;
- 验证服务将用户标识OpenId返回到客户端;
- 客户端进行存储;
- 访问子系统时,将OpenId发送到子系统;
- 子系统将OpenId转发到验证服务;
- 验证服务将用户认证信息返回给子系统;
- 子系统构建用户验证信息后将授权后的内容返回给客户端。
这套单点登录验证机制的主要问题在于他基于C/S架构下将用户的OpenId存储于客户端,在子系统之间发送OpenId,而B/S模式下要做到这一点就显得较为困难。为了处理这个问题我们将引出下一种方式,这种方式将解决B/S模式下的OpenId的存储、传递问题。
基于Cookie的OpenId
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
- 返回用户认证信息
- 返回授权后的内容
B/S多域名环境下的单点登录处理
- 用户通过登录子系统进行用户登录;
- 用户登录子系统记录了用户的登录状态、OpenId等信息;
- 用户使用业务子系统;若用户未登录业务子系统则将用户跳转至用户登录子系统;
- 用户子系统通过JSONP接口将用户OpenId传给业务子系统;
- 业务子系统通过OpenId调用验证服务;
- 验证服务返回认证信息、业务子系统构造用户登录凭证;(此时用户客户端已经与子业务系统的验证信息已经一一对应)
- 将用户登录结果返回用户登录子系统,若成功登录则将用户跳转回业务子系统;
- 将授权后的内容返回客户端;
实际项目使用
-
用户访问前台界面(服务器上访问的是80端口,直接访问域名或ip即可)
-
Vue- router配置的全局路由会界面路由跳转至/home页面。在路由至/home界面时,会触发路由守卫(拦截器)
-
路由守卫判断:如果本地会设置路由守卫菜单(初次登陆未设置),则直接下一步,如果首次登陆未设置登录路由菜单,那么先获取浏览器请求路径的授权码(如果存在),然后会向后台发起请求查询菜单
-
Vue-axios会针对每一个组件进行拦截,在请求头上加上每一个cookie存储的token信息(如果存在的话)
-
后台fmp-sys菜单接收查询菜单的请求,触发请求拦截器(OAuth2Filter)
-
请求拦截器判断
- 如果请求方法是option则不进行拦截
- 获取请求信息中的授权码和token信息,如果授权码和token信息均存在则进行下一步
- 如果授权码存在但token不存在则调用方法发送https请求至单点登录系统获取token信息并进行解析存储至redis
- 如果授权码不存在,且token不存在则代表着是首次访问直接返回403错误码至前台
- 如果授权码不存在,但token存在则代表采用的是非单点登录方式登录
-
请求拦截器校验完授权码和token后,会继续被shiro框架的登录授权拦截器拦截(AuthenticatingFilter),具体的逻辑在OAuth2Realm.java中认证,通过token获取用户编号或id,查询用户的详细信息及对应的角色信息,存入redis中
授权,从redis中获取用户信息,通过id查询用户权限信息存入shiro相关实体对象中 -
通过shiro拦截器后,请求继续访问查询菜单,并将菜单结果和token信息以及操作人所属机构返回值前台。菜单列表会经过函数处理动态生成router,token和机构会保存至本地cookie
-
前台界面会通过axios对每一个请求响应进行拦截
- 如果返回码是403,则代表是首次访问界面,重定向至oa登录界面中(无参数)
- 如果返回码是401,则代表token返回异常(失效等),重定向至oa登陆界面(无参数),如果用户被锁定,亦是在返回码401的判断逻辑中
安全问题
在整个开发过程初期,我们采用用户表中记录一个OpenId字段来保存用户OpenId,而这个机制下很明显存在一些安全性、扩展性问题。这个扩展性问题主要体现在一个方面:OpenId的安全性和用户体验的矛盾。整个单点登录的机制决定了OpenId是会出现在客户端的,所以OpenId需要有过期机制,假如用户在一个终端登录的话可以选择在用户每次登录或者每次退出时刷新OpenId,而在多终端登录的情况下就会出现矛盾:当一个终端刷新了OpenId之后其他终端将无法正常授权。而最终,我采用了单用户多OpenId的解决方案。每次用户通过用户名/密码登录时,产生一个OpenId保存在Redis里,并且设定过期时间,这样多个终端登录就会有多个OpenId与之对应,不再会存在一个OpenId失效所有终端验证都失效的情况。