我這裏實現的sso單點登錄的思路是這樣的 :
用戶必須登錄,才能進行系統的操作(例如商城的購物車系統),如果沒有正確登錄,則跳轉至登錄頁面,提示用戶登錄。 登錄時首先判斷session中是否有記錄,無記錄則再判斷緩存中是否有該用戶的登錄狀態。當用戶正常登錄成功後,將用戶的狀態以一個隨機的uuid作爲key,將該uuid寫入到客戶端的Cookie值作爲value(並且同時在用戶正確登錄的情況下,將用戶狀態記錄到session中)保存到緩存庫中(我這裏使用的時redis作爲緩存庫的),將緩存中的用戶狀態作爲該系統中用戶在各個模塊間的單點登錄依據。
實現單點登錄的核心:必須所有系統共享用戶的登錄狀態。
圖例說明基本是這樣的:
上面是實現sso的基本思路,這裏我再寫一個簡單的小demo大家可以參考下:
1:需要一個user的實體類
public class Users {
private Integer userid;
private String username;
private String pwd;
private String regtime;
private String email;
private Integer status;
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getRegtime() {
return regtime;
}
public void setRegtime(String regtime) {
this.regtime = regtime;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
}
2:提供一個繼承了user類的擴展類import com.ego.sso.domain.Users;
public class UsersVo extends Users {
private String token;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
3:提供一個帶有登錄方法的UsersMapper接口import com.ego.sso.domain.Users;
public interface UsersMapper {
/**
* 驗證用戶的賬戶信息
* **/
public Users loadUsersByUname(String uname);
}
4:mapper.xml 。用以首次登錄從數據庫得到用戶的正確數據
5:提供service的接口,這個我就省略了
6:service的實現類
import java.util.UUID;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ego.sso.domain.Users;
import com.ego.sso.mapper.UsersMapper;
import com.ego.sso.service.UsersService;
import com.ego.sso.vo.UsersVo;
import com.utils.BinaryUtils;
import redis.clients.jedis.Jedis;
@Service
public class UsersServiceImpl implements UsersService {
@Autowired
private UsersMapper usersMapper;
@Autowired
private Jedis jedis;
@Override
public UsersVo loadUsers(UsersVo uvo) {
UsersVo vo=null;
// TODO Auto-generated method stub
Users users = usersMapper.loadUsersByUname(uvo.getUsername());
if(users!=null){
String upwd=users.getPwd();
if(upwd.equals(uvo.getPwd())){
//用戶賬戶信息正確
String token=UUID.randomUUID().toString();
//當前登錄用戶保存到redis數據庫
jedis.set(BinaryUtils.objToByte(token),
BinaryUtils.objToByte(users));
vo = new UsersVo();
BeanUtils.copyProperties(users, vo);
vo.setToken(token);
}
}
return vo;
}
}
當然以上其實只是實現了用戶輸入數據,然後和數據庫對比,看有沒有該用戶的數據而已,並沒有實現sso的單點登錄的登錄狀態共享。所以,這裏我們還要添加一個攔截器,用以實現攔截用戶的登錄請求
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import com.utils.BinaryUtils;
import com.utils.CookieUtil;
import redis.clients.jedis.Jedis;
@Component
public class LoginInterceptor implements HandlerInterceptor {
//注入jedis對象
@Autowired
private Jedis jedis;
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse respObject arg2, Exception arg3) throws Exception {
// TODO Auto-generated method stub
}
@Override
public void postHandle(HttpServletRequest req, HttpServletResponse resp, Object arg2, ModelAndView arg3) throws Exception {
// TODO Auto-generated method stub
}
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object arg2) throws Exception {
jedis.select(0);
//用戶是否能訪問某個資源,控制該方法的返回值
System.out.println("sso_token---------------------preHandle");
//獲得session對象
HttpSession session=req.getSession();
Object obj=session.getAttribute("user");
if(obj==null) {//當前用戶沒有登錄
//獲取客戶端Cookie中的數據
String sso_token=CookieUtil.getCookieValue(req, "sso_token");
System.out.println("sso_token---------------------"+sso_token);
//判斷redis數據庫是否存在用戶登錄狀態
byte[] buf=jedis.get(BinaryUtils.objToByte(sso_token));
if(buf==null||buf.length==0){
resp.sendRedirect("http://localhost:9090/ego_sso");
return false;
}else{
return true;
}
}
return true;
}
}
ps:說下攔截器,我自己都有點忘了。在攔截器中中有三個方法 :
preHandler :在進入Handler方法之前執行了,使用於身份認證,身份授權,登陸校驗等,比如身份認證,用戶沒有登陸,攔截不再向下執行,返回值爲 false ,即可實現攔截;否則,返回true時,攔截不進行執行;
postHandler : 進入Handler方法之後,返回ModelAndView之前執行,使用場景從ModelAndView參數出發,比如將公用的模型數據在這裏傳入到視圖,也可以統一指定顯示的視圖等;
afterHandler : 在執行Handler完成後執行此方法,使用於統一的異常處理,統一的日誌處理等。
這樣的話,在執行每個controller層的方法前,都會先調用該preHandle方法驗證session和緩存庫中是否有該用戶的登錄狀態,從而實現登錄狀態的共享。
時間有點久了,我自己都不太記得具體細節了,就是個思路,寫的比較亂,大家湊合看。。有錯的地方幫我指出了,謝謝