公司的项目完结了,总结下接口安全性问题
webservice安全性验证
思路:
1.移动端启动app后请求的第一个接口是:获取系统消息
请求参数:无
请求头部信息添加 "user-appid":"123456" 这个键值对。123456:移动端随机生成的一个6位的数字。
2.客户端请求 获取系统消息接口,服务器这边的处理
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) 与 "系统消息"一起返回给移动端
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); } }