单点登陆github
github:https://github.com/Handoking/Single-Sign-on
单点登陆的原理
客户端一拦截器
认证中心代码
这里并没有使用数据库,仅仅是使用了数据结构保存了session。同时只是为了实现两个客户端的登陆,所以直接验证了用户名和密码。
数据结构类UserDB.class
package com.handoking.db;
import com.handoking.pojo.ClientInfoVo;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/**
* @ClassName UserDB
* @Description TODO
* @Author Handoking
* @Date 2019/12/27 10:00
**/
public class UserDB {
public static HashSet<String> tokenSet = new HashSet<>();
/**
* 用户登出地址和sessionid。map中key为token,值为多个客户端的登出地址和sessionid组成的列表,比如天猫,淘宝等
*/
public static Map<String,List<ClientInfoVo>> clientInfo=
new HashMap<>();
}
完成登陆、验证登陆、验证令牌,注销登陆。
package com.handoking.controller;
import com.handoking.db.UserDB;
import com.handoking.pojo.ClientInfoVo;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @ClassName SsoServerController
* @Description TODO
* @Author Handoking
* @Date 2019/12/17 17:12
**/
@Controller
public class SsoServerController {
@RequestMapping("/index")
public String index(){
return "login";
}
@RequestMapping("/login")
public String login(String userName,String passwd,HttpSession session,Model model,String redirectUrl){
System.out.println("====================================");
System.out.println("userName:"+userName+"\tpasswd:"+passwd);
if("handoking".equals(userName)&&"123456".equals(passwd)){
//用户存在,验证成功
System.out.println("登陆校验成功");
//生成令牌
String token = UUID.randomUUID().toString();
System.out.println("成功生成token:"+token);
//存到服务器本地
session.setAttribute("token",token);
UserDB.tokenSet.add(token);
//返回给用户 model和?token=传参作用一样,重定向到来的地方
model.addAttribute("token",token);
return "redirect:"+redirectUrl;
}
//用户名或者密码错误,重新登陆,但需要带上从哪里来url,登陆成功后重定向到此url
System.out.println("用户名或密码错误");
model.addAttribute("redirectUrl",redirectUrl);
return "login";
}
@RequestMapping("/checkLogin")
public String checkLogin(HttpSession session, String redirectUrl, Model model){
//1.判断有没有全局会话 token是服务器生成
String token = (String) session.getAttribute("token");
if(StringUtils.isEmpty(token)){
//如果全局会话不存在,返回登陆界面
model.addAttribute("redirectUrl", redirectUrl);
return "login";
}else{
//如果全局会话存在,那么拿到token,重定向到来的url
System.out.println(System.currentTimeMillis()+" 会话存在");
model.addAttribute("token",token);
return "redirect:"+redirectUrl;
}
}
/**
*@params [token, clientUrl, jessionId]
*@return java.lang.String
*@author Handoking
*@date 2019/12/30 16:12
* 验证token的同时,将退出登录的clientUrl和当前session id存到认证中心
*/
@RequestMapping("/verify")
@ResponseBody
public String verify(String token,String clientUrl,String jessionId){
if (UserDB.tokenSet.contains(token)){
System.out.println(System.currentTimeMillis()+" 服务器存在当前token,验证成功");
//将clientUrl和sessionId等用户信息存入认证中心
List<ClientInfoVo> clientList = UserDB.clientInfo.computeIfAbsent(token, k -> new ArrayList<>());
// 以上写法等同于下面的写法
// List<ClientInfoVo> clientList = UserDB.clientInfo.get(token);
// if (clientList == null){
// //第一次判断一定为空
// clientList = new ArrayList<>();
// UserDB.clientInfo.put(token,clientList);
// }
ClientInfoVo vo = new ClientInfoVo();
vo.setClientUrl(clientUrl);
vo.setJessionId(jessionId);
clientList.add(vo);
System.out.println("客户端的登出地址和jession已经添加到list");
return "true";
}
return "false";
}
/**
*@params [session]
*@return java.lang.String
*@author Handoking
*@date 2019/12/30 19:32
* 服务器端注销,使用监听器
*/
@RequestMapping("/logOut")
public String logOut(HttpSession session){
//手动销毁全局会话,前提是session还没有自动注销的。
session.invalidate();
//通知子系统注销,调用所有的子系统,并销毁子系统的session
//使用监听器,监听session是否过期
//销毁session时调用监听器
return "login";
}
}