单点登录三个方法及原理:
- 共享Session
- 基于OpenId的单点登录
- 基于Cookie的OpenId存储方案
共享Session
共享Session可谓是实现单点登录最直接、最简单的方式。将用户认证信息保存于Session中,即以Session内存储的值为用户凭证,这在单个站点内使用是很正常也很容易实现的,而在用户验证、用户信息管理与业务应用分离的场景下即会遇到单点登录的问题,在应用体系简单,子系统很少的情况下,可以考虑采用Session共享的方法来处理这个问题。
基于Redis的Session共享方案。将Session存储于Redis上,然后将整个系统的全局Cookie Domain设置于顶级域名上,这样SessionID就能在各个子系统间共享。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
String JSESSIONID = request.getSession().getId();//获取当前JSESSIONID (不管是从主域还是二级域访问产生)
Cookie cookie = new Cookie("JSESSIONID", JSESSIONID);
cookie.setDomain(".test.com"); //关键在这里,将cookie设成主域名访问,确保不同域之间都能获取到该cookie的值,从而确保session统一
response.addCookie(cookie); //将cookie返回到客户端
request.getRequestDispatcher("indes.jsp").forward(request, response);
%>
基于OpenId的单点登录
- 当用户第一次登录时,将用户名密码发送给验证服务;
- 验证服务将用户标识OpenId返回到客户端;
- 客户端进行存储;
- 访问子系统时,将OpenId发送到子系统;
- 子系统将OpenId转发到验证服务;
- 验证服务将用户认证信息返回给子系统;
- 子系统构建用户验证信息后将授权后的内容返回给客户端。
基于Cookie的OpenId存储方案
- 在提供验证服务的站点里登录;
- 将OpenId写入顶级域名Cookie里;
- 访问子系统(Cookie里带有OpenId)
- 子系统取出OpenId通过并向验证服务发送OpenId
- 返回用户认证信息
- 返回授权后的内容
B/S多域名环境下的单点登录处理
在多个顶级域名的情况下,我们将无法让各个子系统的OpenId共享。处理B/S环境下的跨域问题,我们首先就应该想到JSONP的方案。
验证步骤如下:
- 用户通过登录子系统进行用户登录;
- 用户登录子系统记录了用户的登录状态、OpenId等信息;
- 用户使用业务子系统;
- 若用户未登录业务子系统则将用户跳转至用户登录子系统;
- 用户子系统通过JSONP接口将用户OpenId传给业务子系统;
- 业务子系统通过OpenId调用验证服务;
- 验证服务返回认证信息、业务子系统构造用户登录凭证;(此时用户客户端已经与子业务系统的验证信息已经一一对应)
- 将用户登录结果返回用户登录子系统,若成功登录则将用户跳转回业务子系统;
- 将授权后的内容返回客户端;
安全问题
在整个开发过程初期,我们采用用户表中纪录一个OpenId字段来保存用户OpenId,而这个机制下很明显存在一些安全性、扩展性问题。这个扩展性问题主要体现在一个方面:OpenId的安全性和用户体验的矛盾。
整个单点登录的机制决定了OpenId是会出现在客户端的,所以OpenId需要有过期机制,假如用户在一个终端登录的话可以选择在用户每次登录或者每次退出时刷新OpenId,而在多终端登录的情况下就会出现矛盾:当一个终端刷新了OpenId之后其他终端将无法正常授权。而最终,我采用了单用户多OpenId的解决方案。每次用户通过用户名/密码登录时,产生一个OpenId保存在Redis里,并且设定过期时间,这样多个终端登录就会有多个OpenId与之对应,不再会存在一个OpenId失效所有终端验证都失效的情况。