需求:
当一个应用下有好多子系统都需要来验证用户,如果每次都需要用户去输入密码来验证,第一用户会疯的,客户体验度不好,第二,各个子系统的验证逻辑也会出现问题;所以就需要设计一套实现在一个用户只需要登录一次就可以再次登录另外子系统
解决方法:
单系统:
登录解决方法使用Cookie来实现客户端与服务端之间用户会话数据,但是cookie会话机制是有限制的,体现在cookie的域(网站的域名),即每次客户端向服务端传递的cookie数据是在某个域名下的数据,而不是所有的数据,所以,如果多系统cookie无法来实现。
多系统:
早起采用:采用同域名共享cookie方式来实现
局限性:
1. 应用系统域名必须统一;
2.各个系统使用(至少web容器)的开发技术相同,不然cookie值为不一样,无法维持会话,cookie的方式是无法实现跨语言技术平台登录的,比如java、php、.net系统之间
3. cookie本身不安全现在:单点登录全称Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分。
原理
:sso需要一个认证中心,只要认证中心验证通过的用户名,登陆子系统不需要再次验证,直接登陆。
当用户访问系统1时,发现系统未登陆,则跳转认证中心(携带子系统网站连接作为参数)登陆,验证通过,会生成授权令牌,用户和sso形成全局会话;
验证通过后,sso系统重定向将系统1登陆成功的页面,系统1使用令牌和用户创建局部会话
当访问系统2时,发现该用户未登录,则跳转到认证中,认证中心发现已经登陆,跳转回系统2的登陆页面,并附上令牌,然后跳转到认证中心验证令牌是否有效,有效的进入系统2登陆页面。
用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系
- 局部会话存在,全局会话一定存在
- 全局会话存在,局部会话不一定存在
- 全局会话销毁,局部会话必须销毁
实现
基于java实现
代码主要分为子系统登陆认证和sso认证系统登陆验证
登陆实现
子系统登陆filter
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认证中心,携带子系统的url连接参数
res.sendRedirect("sso-server-url-with-system-url");
}
子系统获取sso认证中心的token值来验证
//紧接上面的filter
// 请求附带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;
}
//如果验证成功则标记当前对话为已登录状态
if (verifyResult) {
session.setAttribute("isLogin", true);
}
chain.doFilter(request, response);
}
verify方法,使用httpClient,需要从新发起http请求验证
http://tzz6.iteye.com/blog/2224757
https://www.cnblogs.com/loveyakamoz/archive/2011/07/21/2112804.html
HttpPost httpPost = new HttpPost("sso-server-verify-url-with-token");
HttpResponse httpResponse = httpClient.execute(httpPost);
sso-client还需将当前会话id与令牌绑定,表示这个会话的登录状态与令牌相关,此关系可以用java的hashmap保存,保存的数据用来处理sso认证中心发来的注销请求
sso认证中心和子系统拦截一样
sso认证中心认证代码和令牌指定
@RequestMapping("/login")
public String login(String username, String password, HttpServletRequest req) {
//认证用户名是否正确和存在
this.checkLoginInfo(username, password);
//设置用户已登录属性
req.getSession().setAttribute("isLogin", true);
//设置令牌值,这里只要区分为该用户一个token值即可
String token = UUID.randomUUID().toString();
return "success";
}
注销实现
子系统filter
String logout = req.getParameter("logout");
if (logout != null) {
this.ssoServer.logout(token);
}
sso认证中心
@RequestMapping("/logout")
public String logout(HttpServletRequest req) {
HttpSession session = req.getSession();
if (session != null) {
session.invalidate();//触发LogoutListener
}
return "redirect:/";
}
??????
sso认证中心有一个全局会话的监听器,一旦全局会话注销,将通知所有注册系统注销
public class LogoutListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent event) {}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
//通过httpClient向所有注册系统发送注销请求
}
}