公司的项目完结了,总结下接口安全性问题
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);
}
}