思路舉例:
以QQ為例:pc端QQ界面中所有的第三方軟件系統,以QQ郵箱舉例,
QQ中成功登陸,從而點擊QQ郵箱可以直接打開網頁進入郵箱並無需登錄
在這里就使用了單點登錄,QQ郵箱就是QQ信任的第三方系統,雙方達成協議,
雙方需要把加密方式,和加密串達成一致。當然如果公司是中型企業,加密串后期
會有運維去和客戶溝通。 接下來貼代碼。
代碼實例:
首先設置一個加密串,這里是在properties文件中定義,嫌麻煩可以直接在接口中定義靜態私有屬性
#秘鑰
AtsecretKey=Activity0CoJUm6123456789
//引入秘鑰
@Value("${AtsecretKey}") private String AtsecretKey; /** * 根據請求的參數加密判斷 * @param currTime * @param idcardno * @param type * @param signature * @return
*/ @RequestMapping("/token") @ResponseBody public ResultEntity checkToken(String currTime,String idcardno,String type,String signature ){ ResultEntity resultEntity=new ResultEntity(); Map<String,String> desMap=new HashMap<>(); desMap.put("currTime",currTime); desMap.put("idcardno",idcardno); desMap.put("type",type); StringBuffer sb = new StringBuffer(); StringBuffer sbkey = new StringBuffer(); Set es = desMap.entrySet(); //所有參與傳參的參數按照accsii排序(升序)
Iterator it = es.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); String k = (String)entry.getKey(); Object v = entry.getValue(); //空值不傳遞,不參與簽名組串
if(null != v && !"".equals(v)) { sb.append(v); sbkey.append(v); } } sbkey=sbkey.append(AtsecretKey); System.out.println("字符串:"+sbkey.toString()); String md5Item = MD5Util.md5(sbkey.toString()); System.out.println("MD5加密值:"+md5Item); System.out.println(sb.toString()+"sign="+md5Item); //拼字符串后加密
if (md5Item.equals(signature)){ long nowTime=System.currentTimeMillis(); if (((nowTime - Long.parseLong(currTime))*1.0 /(1000 * 60))>20){ resultEntity.setState(HttpCode.FAILED); resultEntity.setRetMessage("認證時間超時"); return resultEntity; } DESPlus desPlus=new DESPlus(); String strJson=JsonUtils.toJson(desMap); String desMessage=desPlus.encrypt(strJson); resultEntity.setState(HttpCode.SUCCESS); resultEntity.setMessage("認證通過"); resultEntity.setResult(desMessage); return resultEntity; } resultEntity.setState(HttpCode.FAILED); resultEntity.setMessage("請求失敗"); return resultEntity; }
代碼解釋:
ResultEntity:工具類,用於封裝json數據,返回頁面響應請求
currTime:時間戳
idcardno:身份證號
type:設備類型手機或者pc
signature:加密串,
Map<String,String> desMap=new HashMap<>(); desMap.put("currTime",currTime); desMap.put("idcardno",idcardno); desMap.put("type",type);
將參數放入map中,方便接下來排序
StringBuffer sb = new StringBuffer(); StringBuffer sbkey = new StringBuffer(); Set es = desMap.entrySet(); //所有參與傳參的參數按照accsii排序(升序) Iterator it = es.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); String k = (String)entry.getKey(); Object v = entry.getValue(); //空值不傳遞,不參與簽名組串 if(null != v && !"".equals(v)) { sb.append(v); sbkey.append(v); }
sbkey=sbkey.append(AtsecretKey);
System.out.println("字符串:"+sbkey.toString());
String md5Item = MD5Util.md5(sbkey.toString());
System.out.println("MD5加密值:"+md5Item);
System.out.println(sb.toString()+"sign="+md5Item);
這里需要用的accsii排序,accsii詳細解釋可以去查一下
通過迭代器迭代排序,並且清除空值,然后將值封裝好key,value
另外在排序后的值后面加上加密串(AtsecretKey),生成加密簽名
接着使用md5工具類加密參數
//拼字符串后加密 if (md5Item.equals(signature)){ long nowTime=System.currentTimeMillis(); if (((nowTime - Long.parseLong(currTime))*1.0 /(1000 * 60))>20){ resultEntity.setState(HttpCode.FAILED); resultEntity.setRetMessage("認證時間超時"); return resultEntity; } DESPlus desPlus=new DESPlus(); String strJson=JsonUtils.toJson(desMap); String desMessage=desPlus.encrypt(strJson); resultEntity.setState(HttpCode.SUCCESS); resultEntity.setMessage("認證通過"); resultEntity.setResult(desMessage); return resultEntity; } resultEntity.setState(HttpCode.FAILED); resultEntity.setMessage("請求失敗"); return resultEntity;
這里將加密后得參數和第三方傳的signature判斷是否相同,如果相同證明是信任的第三方,
才可以執行下面的操作
拿到第三方發送的時間戳(currTime)並且獲取當前的時間戳,設置時間戳過期時間,
這里是防止惡意攻擊,不可能第二天還可以拿着前一天的請求來訪問,
成功設置后在使用DES加密,因為des是可以解密的,將之前map的參數在一次用des加密,
轉換成json數據,讓后放進工具類返回請求,第三方拿到參數,再次帶着參數來訪問接口,
/** * 根據第三方的Token來處理單點登錄 * @param request * @param session * @return */ @RequestMapping("/IntoLogin") public String intoLogin( HttpServletRequest request, HttpSession session,String token){ //拿到token解密 DESPlus desPlus=new DESPlus(); try { String decToken=desPlus.decrypt(token); Map<String,String>map=JsonUtils.fromJson(decToken,Map.class); ReaderEntity record = new ReaderEntity(); record.setCardno(map.get("cardno")); record.setIdcardno(map.get("idcardno")); String type=map.get("type"); String currTime=map.get("currTime"); ReaderSession readerSession =null; long nowTime=System.currentTimeMillis(); //判斷過期時長,請求時間超過20分鍾就無效 if (((nowTime - Long.parseLong(currTime))*1.0 /(1000 * 60))<20) { readerSession = readerService.login(record, request); } if(readerSession!=null) { session.setAttribute("readerInfo", readerSession); UnionUserSession unionUserSession = new UnionUserSession(); unionUserSession.setUserType(2); unionUserSession.setUserId(readerSession.getReader().getRecno().toString()); request.getSession().setAttribute(UnionUserSession.SESSION_ID, unionUserSession); //這里判斷密鑰類型是pc還是移動端,登錄后將跳轉不同頁面 if (type.equals("0")){ return "/pc/index"; }else{ return "/mobile/activity/home"; } } }catch (Exception e) { return "/pc/index"; } return "/pc/index"; }
這里的代碼不多做解釋,應該能看得懂,其實這種單點登錄思路要屢清楚
為了安全性參數一定是要加密的,特別是加密協議,密鑰是雙方知道的,第三方
請求的時候也是帶着參數可已經加密的參數來求接口,如果我們拿着他們的參數排序
在加上密鑰以后加密,發現加密后的字符串一致說明是我們信任的第三方,
這里單點登錄要用兩個接口,所以我們需要使用des加密后返回給他們再次請求,
一個接口負責判斷是否第三方,另一個接口登錄,
這里只提供接口供第三方訪問。