一:什麼是sso(single sign on) ? html
sso(單點登陸系統)簡單說就是客戶端第一次訪問應用1的時候,因爲沒有登陸,會被引導到登陸頁面進行登陸,若是登陸校驗經過,將返回一個認證信息ticket,做爲認證憑據。下次客戶端訪問應用2的時候,發送的url請求會攜帶着ticket做爲本身的認證憑據,服務器會將該請求攜帶的ticket交給認證中心進行比對,檢驗,若是檢驗經過,應用2在能夠不登陸的狀況下訪問內部的資源信息。ajax
二:單點登陸系統的整個登陸過程redis
(1)傳統登陸方式(單應用下)json
圖一:爲傳統的登陸方式。在一個工程下這套登陸機制是沒有問題的。可是集羣環境下會出現要求用戶屢次登陸的狀況跨域
如何解決集羣環境中用戶屢次登陸的狀況?tomcat
解決屢次登陸的方案有兩種:就是解決session共享的問題服務器
1.配置tomcat集羣,在tomcat中配置session共享(session複製),可是問題是tomcat部署的節點過多,會出現性能問題。因此通常不適用這種方式cookie
2.可使用session服務器,是每一個節點保持無狀態,保存session信息。模擬session。session
單點登陸就是爲了解決session共享問題提出的一套解決方案。使用redis模擬session,實現session的統一管理。app
(2)使用單點登陸的業務流程
登陸流程解析:
第一步:第一次訪問,進入登陸系統輸入用戶名密碼進行驗證登陸,若是登陸成功,生成Token對象,做爲認證令牌(token至關於原來的jsessionid字符串,這裏使用uuid)
第二步:將返回的token對象信息存入redis服務器。key 就是token , value就是登陸用戶的信息
第三步:既然是模擬session,因此也須要設置key的過時時間。
第四步:將token(存儲的key)寫入cookie中,做爲用戶請求的url參數信息
第五步:用戶第二次訪問,首先檢查用戶是否登陸,將寫入cookie中的token做爲請求參數,服務器會從url中解析token的值,而後將解析的token值做爲key查詢redis服務器;若是查詢結果爲空,表示session已通過期,要求客戶端跳轉到登陸頁面完成登陸操做;若是查詢結果不爲空,須要將查詢的信息(登陸用戶的信息)做爲對象返回,而後從新設置key的過時時間。
第六步:解決跨域的問題,使用js發送ajax請求, 使用jsonp解決跨域問題。須要服務器返回的數據格式爲mycallback:{id:xx,name:xx},因此將返回的json數據進行拼接成要求的格式便可。
三:登陸過程的核心代碼
@Override public E3Result login(String username, String password) { try { TbUserExample example = new TbUserExample(); Criteria criteria = example.createCriteria(); criteria.andUsernameEqualTo(username); List<TbUser> list = userMapper.selectByExample(example); if (list == null || list.size() == 0) { // 登陸失敗 return E3Result.build(400, "用戶名或密碼錯誤"); } //得到用戶對象 TbUser user = list.get(0); //校驗用戶密碼 if(!(user.getPassword()).equals(DigestUtils.md5Hex(password.getBytes()))) { //校驗失敗 return E3Result.build(400, "用戶名或者密碼錯誤"); } //登陸成功 //1.建立token對象,使用uuid String token = UUID.randomUUID().toString(); //2.將uuid做爲key,用戶信息做爲value值存入redis中 jedisClient.set("USER_INFO:"+token, JsonUtils.objectToJson(user)); //3.設置過時時間,半小時 jedisClient.expire("USER_INFO:"+token, 1800); //4.返回登陸成功的信息 return E3Result.ok(token); } catch (Exception e) { e.printStackTrace(); } return null; }
解決跨域問題的服務端
@RequestMapping(value = "/user/token/{token}", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody public String getTokenName(@PathVariable String token, String callback) { E3Result result = tokenService.getToken(token); if (StringUtils.isNotBlank(callback)) { // 拼接成頁面須要的數據給事 // mycall({id:1,name:z}); String json = callback + "(" + JsonUtils.objectToJson(result) + ");"; System.out.println(json); return json; } return JsonUtils.objectToJson(result); }
客戶端代碼:
var E3MALL = { checkLogin : function(){
//COOKIE_TOKEN_KEY設置的cookie的name值 var _ticket = $.cookie("COOKIE_TOKEN_KEY"); if(!_ticket){ return ; } $.ajax({ url : "http://localhost:8088/user/token/" + _ticket, dataType : "jsonp", type : "GET", success : function(data){ if(data.status == 200){ var username = data.data.username; var html = username + ",歡迎!<a href=\"http://www.e3mall.cn/user/logout.html\" class=\"link-logout\">[退出]</a>"; $("#loginbar").html(html); } } }); } } $(function(){ // 查看是否已經登陸,若是已經登陸查詢登陸信息 E3MALL.checkLogin(); });