移动应用开发过程中请求服务端采用token(在计算机身份认证中是令牌(临时))方式请求方式进行,get请求方式下token直接暴露在请求路径很容易被别人利用进行篡改进行重复提交等,怎样保证移动端安全成为后台开发者所面临的问题,,因为涉及敏感行业数据APP接口开发过程中安全性成为要求,在网上看了很多资料最后选择采用token+time+nonce+sign方式在过滤器层进行校验,APP进行拼接加密,后端Filter进行解密校验,优点实现简单,能够很好保证请求过程中APP端到服务器端安全性,因此此种校验方式被很多互联网公司所采用。
一、技术实现原理:
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校验规则代码,在开发过程中可以参考此代码进行适当修改进行使用
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。