移動應用開發過程當中請求服務端採用token(在計算機身份認證中是令牌(臨時))方式請求方式進行,get請求方式下token直接暴露在請求路徑很容易被別人利用進行篡改進行重複提交等,怎樣保證移動端安全成爲後臺開發者所面臨的問題,,由於涉及敏感行業數據APP接口開發過程當中安全性成爲要求,在網上看了不少資料最後選擇採用token+time+nonce+sign方式在過濾器層進行校驗,APP進行拼接加密,後端Filter進行解密校驗,優勢實現簡單,可以很好保證請求過程當中APP端到服務器端安全性,所以此種校驗方式被不少互聯網公司所採用。redis
token: token爲用戶登陸時獲取的臨時令牌
time: APP獲取到的當前時間,形式爲long類型,time時間可進行匹配APP獲取時間和服務器時間差若是大於某個時間則失效(如連接超過60秒失效,具體可參考源碼),可防止別人截取數據後修改數據內容進行提交
nonce:APP接口經過UUID等形式獲取的隨機不重複內容,能夠講nonce存放在session或redis中並設置合適的超時時間,而後下次請求時經過nonce取session或redis中是否存在,若是存在則認爲重複提交請求,防止被別人篡改後提交數據
sign: 經過token+time+nonce三個參數加密後的密文(sign加密可以使用使用ase,md5等加密方式,須要注意md5加密不可逆,實現過程有所區別),sign主要校驗token、time、nonce有沒有被別人篡改數據後端
1在過濾層實現SecurityFilter類 public class SecurityFilter extends Filter { private static String secretKey = ApplicationConfig.secretKey; private static String ivParameter = ApplicationConfig.ivParameter ; // 本文采用ase加密 將加密secretKey及ivParameter 的保存在properties文件中,使用公共接口加載,用戶能夠選擇其餘加密方式 @Override protected void doDestroy() { // TODO Auto-generated method stub } @Override protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws Throwable { String requestUri = request.getRequestURI(); //登陸接口則直接不適用 if(requestUri.indexOf("applogin")>-1){ chain.doFilter(request, response); } else { MsgEntity msg = new MsgEntity(); // 獲取request的URL,用於判斷該請求是否超時 String time = request.getParameter("time"); // 獲取請求nonce,用於判斷是否用戶重複提交 String nonce = request.getParameter("nonce"); // 獲取token碼,實際傳入token值 String token = request.getParameter("token"); // 獲取sign,經過time,nonce,token合併進行加密後密文,須要注意加密順序,平臺必須與APP一致 String sign = request.getParameter("sign"); // 判斷數據是否存在 if (StringUtils.isEmpty(time) || StringUtils.isEmpty(nonce) || StringUtils.isEmpty(token) || StringUtils.isEmpty(sign)) { msg.setMsg("登陸失敗"); msg.setState(false); HttpRequestUtils.writeResponseJsonString(response, msg); return; } //在實際開發過程當中傳入數據發現存在%號沒法進行解密,須要進行URLDecoder.decode進行解碼 if (sign.indexOf("%") > -1) { sign = URLDecoder.decode(sign, "UTF-8"); } // 判斷請求是否超過60秒,超過60秒則次請求連接失效,返回登陸失敗,可根據網絡狀況適當延長或者縮短期 if (difference(time) > 60) { msg.setMsg("登陸失敗"); msg.setState(false); HttpRequestUtils.writeResponseJsonString(response, msg); return; } // 判斷是不是重複請求,若是請求nonce存在則登陸失敗,不存在則將nonce存在redis中或session中並設置合適超時時間 if (JedisUtils.exists(nonce)) { msg.setState(false); msg.setMsg("登陸失敗"); HttpRequestUtils.writeResponseJsonString(response, msg); return; } else { JedisUtils.set(nonce, "0", 70); } // aes 解密,sign和token + time + nonce進行比較判斷數據數據是否存在篡改 String mingwei = ""; try { mingwei = AesUtils.decrypt(sign); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } if (!mingwei.equals(token + time + nonce)) { msg.setState(false); msg.setMsg("登陸失敗"); HttpRequestUtils.writeResponseJsonString(response, msg); return; } //若是請求所有驗證經過則放行 chain.doFilter(request, response); }
//主要用於系統時間-APP發送數據時生成日期獲得當前差值(秒) public int difference(String past) { long newTimestamp = new Date().getTime(); return Math.abs((int) (newTimestamp - Long.parseLong(past)) / 1000); } } /** * aes 解密 * * @param sSrc * @return * @throws Exception */ public static String decrypt(String sSrc) throws Exception { try { byte[] raw = secretKey.getBytes("ASCII"); SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES"); Cipher cipher = Cipher.getInstance("AES / CBC / PKCS5Padding"); IvParameterSpec iv = new IvParameterSpec(ivParameter.getBytes()); cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); byte[] encrypted1 = new BASE64Decoder().decodeBuffer(sSrc);// 先用base64解密 byte[] original = cipher.doFinal(encrypted1); String originalString = new String(original, "UTF-8"); return originalString; } catch (Exception ex) { return null; } }
以上爲過濾實現token校驗規則代碼,在開發過程當中能夠參考此代碼進行適當修改進行使用安全