公司的項目完結了,總結下接口安全性問題java
webservice安全性驗證android
思路:web
1.移動端啓動app後請求的第一個接口是:獲取系統消息
算法
請求參數:無spring
請求頭部信息添加 "user-appid":"123456" 這個鍵值對。123456:移動端隨機生成的一個6位的數字。數組
2.客戶端請求 獲取系統消息接口,服務器這邊的處理spring-mvc
1>接收解析頭部消息安全
解析出user-appid的值,而後對其進行DES加密服務器
@RestController public class SysInfoController { @Autowired private SysInfoService sysInfoServiceImpl; /** * 獲取系統信息 * * @return 系統信息 */ @RequestMapping(value = "/getSysInfo", method = RequestMethod.GET) public ResultObject getSysInfo(HttpServletRequest request) { System.out.println("請求路徑:/getSysInfo"); ResultObject resultObject = new ResultObject(); resultObject.setResultCode(ResultCode.SUCCESS); resultObject.setResultMsg(ResultMsg.MSG_SUCCESS); SysInfoRel sysInfoRel = sysInfoServiceImpl.getSysInfos(); //獲取請求頭部信息 Enumeration<String> headerNames = request.getHeaderNames(); String key = ""; String userToken = ""; while (headerNames.hasMoreElements()) { key = (String) headerNames.nextElement(); if("user-appid".equals(key.toLowerCase())){ try{ //對user-appid進行加密,算法是DES userToken = DesUtil.encrypt(request.getHeader(key)); }catch(Exception e){ sysInfoRel = null; e.printStackTrace(); } break; } } if (sysInfoRel != null && !StringUtils.isEmpty(userToken)) { sysInfoRel.setUserToken(userToken); resultObject.setData(sysInfoRel); } else { resultObject.setResultCode(ResultCode.FAILED); resultObject.setResultMsg(ResultMsg.MSG_FAILED); } return resultObject; } }
2>將加密後的值(取名:userToken) 與 "系統消息"一塊兒返回給移動端mvc
3>之後,移動端每次請求本項目的其餘接口時,都在頭部信息中傳user-appid和userToken過來
4>服務器這端對userToken解密(DES解密算法),而後與user-appid進行比較
4-1>相等,則執行請求操做
4-2>不相等,則返回500錯誤
備註:步驟4>是在攔截器中進行的,攔截器代碼以下
package com.zhiji.caren.interceptor; /** * 程序名 CRRequestInterceptor.java * 程序功能 MVC攔截器操做類 * 做成者 xxx * 做成日期 2015-12-21 * ======================修改履歷====================== * 項目名 狀態 做成者 做成日期 * -------------------------------------------------- * caren 新規 xxx 2015-12-21 * ================================================= */ import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import com.zhiji.caren.common.Constant; import com.zhiji.caren.utils.DesUtil; public class CRRequestInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // TODO Auto-generated method stub //攔截器設置 //0:關閉攔截器 --測試時使用 //1:開啓攔截器 --發佈後使用 //該方法返回true時,表示驗證經過,能夠執行請求接口操做 if(Constant.switchFlag == 0){ return true; } // 獲取訪問的頭部數據header String userAgent = ""; String userToken = ""; String userAppID = ""; String specAppID = "com.cheqiren.cms"; String contextPath = request.getPathInfo(); // 默認未包含userToken boolean hasUserTokenFlag = false; boolean hasUserAppIDFlag = false; Enumeration<String> headerNames = request.getHeaderNames(); //循環取頭部信息 while (headerNames.hasMoreElements()) { String key = (String) headerNames.nextElement(); if("user-agent".equals(key.toLowerCase())){ userAgent = request.getHeader(key); } if("user-token".equals(key.toLowerCase())){ hasUserTokenFlag = true; userToken = request.getHeader(key); } if("user-appid".equals(key.toLowerCase())){ hasUserAppIDFlag = true; userAppID = request.getHeader(key); } } //所有經過後執行 if(hasUserTokenFlag && hasUserAppIDFlag){ // 容許後臺訪問接口 if(specAppID.equals(userAppID) || contextPath.contains("getSysInfo")){ return true; } // 訪問客戶端爲移動端時且受權碼符合規則,容許訪問接口 //最後一項是對userToken進行解密 if(userAgent.toLowerCase().contains("mobile") && (userAgent.toLowerCase().contains("iphone") //|| userAgent.toLowerCase().contains("iPad") || userAgent.toLowerCase().contains("android")) && userAppID.equals(DesUtil.decrypt(userToken))){ return true; } } response.sendError(500,"非法訪問!"); return false; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // TODO Auto-generated method stub } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // TODO Auto-generated method stub } }
攔截器須要配置在spring-mvc文件中(關於這個配置文件,本博客其餘章節有詳細講解),配置以下
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**" /> <!-- 定義在mvc:interceptor下面的表示是對特定的請求才進行攔截的 --> <bean class="com.zhiji.caren.interceptor.CRRequestInterceptor" /> </mvc:interceptor> </mvc:interceptors>
最後,總結一下流程
1.移動端啓動app發起調用 獲取系統信息 請求
頭部消息包含
user-appid:隨機值
user-token:隨機值
2.進入攔截器
2.1攔截器取出user-appid值和user-token值
這兩個值同時true後,判斷路徑是否包含 getSysInfo
2.1.1包含,直接經過,去執行 獲取系統信息 接口的操做 --針對 獲取系統信息 接口
2.1.2不包含,則判斷是否是移動端發起的請求。--針對 本項目其餘接口
3.執行 獲取系統信息 接口
取出頭部信息user-appid,對其DES加密,傳給移動端
4.移動端請求其餘接口
user-appid:隨機值 --與請求獲取系統信息接口時傳的值同樣
user-token:用 系統信息接口返回的userToekn值替代
4.1 進入攔截器
4.1.1 取出user-appid值和user-token值
4.2.2 判斷訪問對象是否爲移動端 且 判斷受權碼(user-token)是否正確
4.2.3 正確,經過攔截器;不然,返回500錯誤
自此,流程結束。
以下部分講述的是DES算法,沒時間仔細研究,先貼上代碼
package com.zhiji.caren.utils; /** * 程序名 DesUtil.java * 程序功能 DEC加密解密類 * 做成者 xx * 做成日期 2016-01-11 * ======================修改履歷====================== * 項目名 狀態 做成者 做成日期 * -------------------------------------------------- * caren 新規 xx 2016-01-11 * ================================================= */ import java.io.IOException; import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; @SuppressWarnings("restriction") public class DesUtil { private final static String DES = "DES"; private final static String SEC_KEY = "readygo-tec.com"; //測試使用 public static void main(String[] args) throws Exception { String data = "DSD12345"; System.err.println(encrypt(data)); System.err.println(decrypt(encrypt(data))); } /** * Description 根據鍵值進行加密 * @param data * @param key 加密鍵byte數組 * @return * @throws Exception */ public static String encrypt(String data) throws Exception { byte[] bt = encrypt(data.getBytes(), SEC_KEY.getBytes()); String strs = new BASE64Encoder().encode(bt); return strs; } /** * Description 根據鍵值進行解密 * @param data * @param key 加密鍵byte數組 * @return * @throws IOException * @throws Exception */ public static String decrypt(String data) throws IOException, Exception { if (data == null) return null; BASE64Decoder decoder = new BASE64Decoder(); byte[] buf = decoder.decodeBuffer(data); byte[] bt = decrypt(buf,SEC_KEY.getBytes()); return new String(bt); } /** * Description 根據鍵值進行加密 * @param data * @param key 加密鍵byte數組 * @return * @throws Exception */ private static byte[] encrypt(byte[] data, byte[] key) throws Exception { // 生成一個可信任的隨機數源 SecureRandom sr = new SecureRandom(); // 從原始密鑰數據建立DESKeySpec對象 DESKeySpec dks = new DESKeySpec(key); // 建立一個密鑰工廠,而後用它把DESKeySpec轉換成SecretKey對象 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES); SecretKey securekey = keyFactory.generateSecret(dks); // Cipher對象實際完成加密操做 Cipher cipher = Cipher.getInstance(DES); // 用密鑰初始化Cipher對象 cipher.init(Cipher.ENCRYPT_MODE, securekey, sr); return cipher.doFinal(data); } /** * Description 根據鍵值進行解密 * @param data * @param key 加密鍵byte數組 * @return * @throws Exception */ private static byte[] decrypt(byte[] data, byte[] key) throws Exception { // 生成一個可信任的隨機數源 SecureRandom sr = new SecureRandom(); // 從原始密鑰數據建立DESKeySpec對象 DESKeySpec dks = new DESKeySpec(key); // 建立一個密鑰工廠,而後用它把DESKeySpec轉換成SecretKey對象 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DES); SecretKey securekey = keyFactory.generateSecret(dks); // Cipher對象實際完成解密操做 Cipher cipher = Cipher.getInstance(DES); // 用密鑰初始化Cipher對象 cipher.init(Cipher.DECRYPT_MODE, securekey, sr); return cipher.doFinal(data); } }