SSO技术架构(Single Sign On)
1.SSO的背景
在多系统共存的环境下,用户在一处登录后,就可以不用在其他系统中重新登录,有了SSO技术,用户的使用成本更低、更加友好。
比如我们平时使用的淘宝、天猫、支护宝这三个应用,只需要登录其中一个应用,我们就可以直接访问这三个系统,而无需重新登录。
技术特点总结
一个账户,一次登录,即可访问多个系统
2.HTTP会话机制的简单介绍
2.1.http的无状态协议
在http协议中,是属于无状态的,这就意味着任何用户都能够访问服务器资源!
但是对于有些系统而言,我们服务器端是需要区分出用户的,比如支付宝、淘宝等系统
2.2.http的会话(session)机制
既然http协议是无状态的,那么我们是否可以引入有状态协议呢?
那么会话协议怎么来实现呢?
- cookie
- 请求参数
2.2.多系统的复杂性
在前面的部分,我们介绍了关于单个系统,是如何实现会话机制的。那么多系统该如何处理呢?
web系统发展为多系统组成的应用群,但是这个复杂度应该由系统内部承担,而不是用户。如果每次访问一个新系统,都需要重新登录一次,对于用户体验而言,是难以接受的!
既然如此,我们是否能够实现在多系统间,登陆一次,即可所有系统都能够访问呢?
在前面的部分,我们提到过,单系统的会话机制,可以使用请求参数、cookie的技术来实现,但是在真实的环境中,基本不采用请求参数的方式,原因很简单,请求参数的方式安全性太差了,非常容易受到攻击。但是cookie是有限制的,cookie的域受限于相同的域名!比如下面的场景
用户访问(天猫)http://tmall.com/,(淘宝)http://taobao.com,(支付宝)https://www.alipay.com/三个域名时
三个域名的cookie是不能够跨域名携带传输的
也就是,如果使用常规的cookie技术,是无法解决多系统的会话机制的。
那么是否可以统一这些系统到一个顶级域名下面呢,比如:*.baidu.com?是的,该方案能够实现,但是存在一个比较大的缺点,那就是对域名有着非常严格的要求!
3.大杀器,SSO
相比较单系统登录,SSO架构需要一个单独的认证中心,用于处理用户的登录。其他的系统不提供登录入口,只需要接受认证中心的间接授权即可。
3.1.单点登录
用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心。全局会话与局部会话存在的约束关系:
- 局部会话存在,全局会话一定存在
- 全局会话存在,局部会话不一定存在
- 全局会话销毁,局部会话必须销毁
3.2.单点注销
在一个子系统中注销,那么其他子系统的会话都会被注销
3.3.部署架构
在这里面,很关键的一点就是区分:sso-client、sso-server
3.4.系统架构
4.SSO的实现
sso-client的功能
- 拦截系统的请求,如果未登录,直接跳转到sso-server登录界面
- 接收并存储sso-server发送过来的令牌
- 与sso-server保持通信,检验令牌的有效性
- 建立局部会话
- 拦截用户的注销请求,向sso-server发送注销请求
- 接收sso-server发送的注销请求,摧毁局部会话
sso-server的功能
- 提供登录界面
- 认证用户的登录信息,创建授权令牌,发送令牌给sso-client
- 创建全局会话
- 检验sso-client发送过来的令牌有效性
- 系统注册
- 接收sso-client的注销请求,注销全局会话,通知sso-client注销局部会话
核心代码
1.sso-client拦截未登录请求
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
HttpSession session = req.getSession();
if (session.getAttribute("isLogin")) {
chain.doFilter(request, response);
return;
}
//跳转至sso认证中心
res.sendRedirect("sso-server-url-with-system-url");
}
2.sso-server创建令牌
String token = UUID.randomUUID().toString();
3.sso-client取得令牌并去sso-server校验令牌的有效性
// 请求附带token参数
String token = req.getParameter("token");
if (token != null) {
// 去sso认证中心校验token
boolean verifyResult = this.verify("sso-server-verify-url", token);
if (!verifyResult) {
res.sendRedirect("sso-server-url");
return;
}
chain.doFilter(request, response);
}