單點登陸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";
}
}