如今不少移動應用須要與後臺交互,與後臺交互就須要身份認證。最初想到用token的方式是當初看到微信公衆平臺的身份認證時採用了token這樣的一個參數,而後本身也照着實現了一個。
說到這個token機制,一個是調用端,一個是後臺。調用端我用JAVA語言開發的Android應用來展現,後臺用J2EE來展現。html
1、token的獲取
token的獲取其實就是移動端的登陸過程,在登陸的時候要先向後臺獲取一個token值,首先來看登陸時Android端的代碼:前端
String name = (EditText)mName.getText().toString(); String password = (EditText)mPsw.getText().toString(); String url2 = "http://"+ip+":"+port+"/test/be/login.do?method=login"; OkHttpClientManager ohm = OkHttpClientManager.getInstance(); OkHttpClientManager.Param[] params = new OkHttpClientManager.Param[]{ new OkHttpClientManager.Param("name", name), new OkHttpClientManager.Param("password", password)}; OkHttpClientManager.ResultCallback cb = new OkHttpClientManager.ResultCallback<String>() { @Override public void onError(Request request, Exception e) { String resultMsg = "請求失敗,請檢查網絡鏈接!"; ToastUtil.showToastMsgError(LoginActivity.this, resultMsg); cancleAnimation();//關閉登陸等待框 } @Override public void onResponse(String response) { JSONObject returnJson = JSONObject.parseObject(response); String resultId = returnJson.get("resultId").toString(); if (resultId.equals("00")) { token = returnJson.get("token").toString(); System.out.println("======================token:" + token); startApp();//進入應用 } else { token = ""; String resultMsg = returnJson.get("resultMsg").toString(); ToastUtil.showToastMsgError(NewLoginActivity.this, resultMsg); cancleAnimation(); } } }; OkHttpClientManager.getInstance().postAsyn(url2,cb, params);
OkHttpClientManager是通過封裝的OkHttp的調用類,能夠直接調用向後臺發起http請求。java
而後咱們來看下後臺的處理,這裏後臺有幾個注意點:首先,login接口必須不被攔截器和過濾器給攔住,而後將用戶名和密碼傳輸到ctrl層,而後驗證該用戶名密碼的真實有效性,若是合適身份,返回token,若是身份有問題,返回相應的提示信息。web
@RequestMapping(params="method=login") @ResponseBody public String login(HttpServletRequest request,HttpServletResponse response,SysUser command) throws Exception{ String resStr = ""; try{ String name = command.getName(); String password = command.getPassword(); UserSession userSession=new UserSession(); // 設置usersession登陸模式 userSession.setDlms("1"); command.setDlms("1"); SysUser sysuser=new SysUser(); Department department=new Department(); if(name==null||name.length()<1||name.indexOf("'")!=-1){ throw new Exception("該用戶不存在!"); } if(password==null||password.length()<1||password.indexOf("'")!=-1){ throw new Exception("該密碼不存在!"); } String strRemoteAddr=FuncUtil.getRemoteIpdz(request); // 獲取ip地址 System.out.println("strRemoteAddr:"+strRemoteAddr); sysuserManager.validateSysuser(command,userSession,strRemoteAddr);//校驗用戶名、密碼,有問題會直接throw異常 sysuser=this.sysuserManager.getSysuserByUsername(name);//獲取用戶信息 if(sysuser==null){ throw new Exception(Constants.SYS_NO_RIGHT); } sysuser.setIp(strRemoteAddr);// 設置登陸人員的ip地址 sysuser.setDlms(command.getDlms()); department=this.departmentManager.getDepartment(sysuser.getGlbm()); if(department==null) throw new Exception(Constants.SYS_NO_HIGHXZQH); UUID token = UUID.randomUUID(); userSession.setSysuser(sysuser);//sysuser userSession.setDepartment(department); userSession.setYhdh(sysuser.getYhdh()); userSession.setGlbm(department.getGlbm()); userSession.setScdlsj(new Date().getTime()+""); RestfulSession.setSession(token.toString(), userSession); Map<String, Object> resultMap = new HashMap<String, Object>(); resultMap.put("resultId", "00"); resultMap.put("user", sysuser); resultMap.put("token", token.toString()); resStr = JSON.toJSONString(resultMap);//返回toekn和用戶信息 }catch(Exception e){ Map<String, Object> resultMap = new HashMap<String, Object>(); resultMap.put("resultId", "01"); resultMap.put("resultMsg", e.getMessage()); resStr = JSON.toJSONString(resultMap);//返回異常 } return resStr; }
RestfulSession,這個類中的靜態參數主要就是用來緩存正在登陸中的用戶的token與對應的sessionredis
package com.tmri.framework.bean; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.StringUtils; import com.tmri.share.frm.bean.UserSession; public class RestfulSession{ private static Map<String,UserSession> session=new HashMap<String,UserSession>(10000);//最多放一萬個用戶信息 private static Map<String,String> clients=new HashMap<String,String>(); public static UserSession getSession(String sessionId){ return session.get(sessionId); } public static void setSession(String sessionId,UserSession clientUser){ String yhdh=clientUser.getYhdh(); String token=clients.get(yhdh); if(StringUtils.isNotEmpty(token)){ session.remove(token); } session.put(sessionId,clientUser); clients.put(clientUser.getYhdh(),sessionId); } public static void renewal(String sessionId){ session.get(sessionId).setScdlsj(new Date().getTime()+「」); } }
2、token的調用
登陸後每次向後臺的請求都要帶上這個token值。若是移動端規定時間(暫時定義爲半小時)內沒有向後臺發起請求,該token做廢,後臺返回token超時的提示。說到這裏,明眼人也看出來了,其實這個機制也是模仿J2EE的session機制。apache
如下是Android端登陸後向後臺發起訪問的代碼,必定要帶着token:json
String creatUrl = "http://"+ip+":"+port+"/test/be/test.do?method=mytest"+"&token="+token; OkHttpClientManager.ResultCallback cb = new OkHttpClientManager.ResultCallback<String>() { @Override public void onError(Request request, Exception e) { ToastUtil.showToastMsgError(TestActivity.this, "迴應服務器失敗"); } @Override public void onResponse(String response) { JSONObject returnJson = JSONObject.parseObject(response); String resultId = returnJson.get("resultId").toString(); String resultMsg = ""; if(resultId.equals("00")){ resultMsg = "提交結果成功"; ToastUtil.showToastMsgError(TestActivity.this, resultMsg); } else if(resultId.equals("98")) // 會話過時 { relogin("當前鏈接已過時,是否從新登錄?"); }else{ //tasklist.clear(); resultMsg = "提交結果失敗:"+returnJson.get("resultMsg").toString(); ToastUtil.showToastMsgError(TestActivity.this, resultMsg); } } }; OkHttpClientManager.getInstance().getAsyn(creatUrl,cb);
訪問的時候除了加入白名單的url,其餘請求都要通過攔截器的過濾,攔截器要判斷此次的請求是移動端訪問(帶token的),仍是通常的網頁訪問,有報錯的話返回的東西也不同,移動端訪問報錯的話返回的是定義好格式的json字符串,而網頁訪問的話返回的通常是報錯頁面(其實,若是web端用前端框架的話,也能夠是定義好格式的json字符串)。
如下是後臺的攔截器中的代碼:緩存
Long timeout = (long) 1800000;//超時時間,30min UserSession userSession; String token = request.getParameter("token"); if(StrUtil.checkBN(token)){ // 有token,接口訪問 boolean flag=false; try{ userSession=RestfulSession.getSession(token); if(userSession!=null){ String currentURL=request.getServletPath(); if(!userSession.getMenuList().contains(currentURL+"?method="+request.getParameter("method"))){ sendRedirectJSON(request,response,"該用戶沒有權限"); return false; } Calendar now=Calendar.getInstance(); if(now.getTime().getTime()-Long.parseLong(userSession.getScdlsj())<timeout){// 若是該用戶最後訪問時間在30分鐘之內 flag=true;// 容許訪問 RestfulSession.renewal(token);//重置該次登陸的session計時 }else{ sendRedirectJSON(request,response,"已超時"); return false; } } }catch(Exception e){ sendRedirectJSON(request,response,"請從新登陸"); } if(!flag){ sendRedirectJSON(request,response,"未登陸"); return false; }else{ return true; } }else{ //正常網頁訪問 userSession=(UserSession)request.getSession().getAttribute("userSession"); if(userSession==null){ sendRedirect(request,response); return false; } String url=""; if(StrUtil.checkBN(request.getParameter("method"))){ url=handler.getClass().getName().substring(handler.getClass().getName().lastIndexOf(".")+1,handler.getClass().getName().length())+"."+request.getParameter("method"); }else{ url=handler.getClass().getName().substring(handler.getClass().getName().lastIndexOf(".")+1,handler.getClass().getName().length()); } HashMap<String,String> rights=userSession.getRights(); if(rights==null){//沒有權限 sendRedirect(request,response); return false; }else{ if(rights.containsKey(url)){ return true; }else{//沒有權限 sendRedirect(request,response); return false; } } }
3、其餘
寫這篇blog的時候特地去搜了一下相關相似的文章,但願有所借鑑學習。
其中有如下的一篇,他採用了redis來緩存token的信息,這個在短期登陸用戶數量達到必定規模的時候應該是個不錯的方案。
redis緩存token信息前端框架