向外网开发的接口,由于外网环境的复杂,容易被人获取到请求地址,以及真是的请求参数,就能够使用该参数再次请求,造成重放攻击;或者黑客直接篡改请求参数。
为了防止请求参数被篡改,需要对参数进行加密。
这里采用的是每次请求根据月的的密钥生成签名sign,在请求到达后台服务器时对请求参数再次进行校验生成签名,比较两次签名。如果一致则认为请求参数没有被篡改,如果不一致则认为请求参数已经被篡改,拦截请求。
下面代码实现
public class RequestCryptoHelper {
//密钥
private static String signKeys = "xxxxxxx";
public static String sha1(String value) {
if (value == null) {
return null;
}
try {
MessageDigest digest = java.security.MessageDigest.getInstance("SHA-1");
digest.update(value.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 这里对请求参数字符串进行处理,去掉所有的空格,和双引号
* 以防止不同系统对于参数处理不同
*/
public static String crypto(String signString){
signString = signString + signKeys;
signString = signString.replaceAll("\"", "")
.replaceAll(" ", "");
return RequestCryptoHelper.sha1(signString.toLowerCase());
}
}
这里是spring boot项目,所以拦截器继承了HandlerInterceptorAdapter实现请求的拦截处理,
这里同时将时间戳一起传递,拦截超时请求,这里设置的超时时间是30秒。
@Component
public class RequestParamVerifyInterceptor extends HandlerInterceptorAdapter {
//这里是对springmvc中路径传参的加密,约定路径传参,在请求头加上(pathvariable: 路径)
//在参数加密时也一起加密
private static final String PATH_VARIABLE = "pathvariable";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
try {
Map<String, Object> params = new HashMap<>();
Map<String, String[]> parameterMap = request.getParameterMap();
Iterator<String> iterator = parameterMap.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
String[] valueArr = parameterMap.get(key);
//这里约定参数不重复
params.put(key, valueArr[0]);
}
String pathvariable = request.getHeader(PATH_VARIABLE);
StringBuffer requestURL = request.getRequestURL();
if(pathvariable != null) {
if(!requestURL.toString().endsWith(pathvariable)) {
throw new Exception("error:路径传参错误!");
}
if(params.containsKey(PATH_VARIABLE))
params.put(PATH_VARIABLE, pathvariable);
}
if (request.getHeader("sign") == null || request.getHeader("timestamp") == null) {
throw new Exception("error:参数错误,缺少必要参数!");
}
long timestamp = Long.parseLong(request.getHeader("timestamp"));
long now = System.currentTimeMillis();
long diffMillis = now - timestamp;
/// 30秒
if ( diffMillis >= 30*1000 ) {
throw new Exception("error:请求失效!");
}
Set<String> keysSet = params.keySet();
Object[] keys = keysSet.toArray();
Arrays.sort(keys);
StringBuffer temp = new StringBuffer();
for (Object key : keys) {
if(key.equals("sign") || key.equals("timestamp")){
continue;
}
temp.append("&");
temp.append(key).append("=");
Object value = params.get(key);
String valueString = "";
if (null != value) {
valueString = String.valueOf(value).toLowerCase();
}
temp.append(valueString);
}
String sign = request.getHeader("sign");
String signString = temp.toString().substring(1)+"×tamp="+timestamp;
String signedString = RequestCryptoHelper.crypto(signString);
boolean result = sign.equals(signedString) ? true : false;
return result;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
注意:
这里需要注意的是,由于参数从前台传到后台,顺序可能发生改变。所以需要对参数进行排序,代码示例中按照参数名排序。
将拦截器注册到springboot项目中。
@Configuration
public class WebAppConfigurer extends WebMvcConfigurerAdapter{
@Autowired
private RequestParamVerifyInterceptor requestParamVerifyInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(requestParamVerifyInterceptor).addPathPatterns("/**");
}
}