HTTP分为两种请求,GET以及POST。现如今,请求中都会带有大量的验证字符。
我验证的方式一般分为两种:
1。用当前的时间生成一个时间戳,然后用这个时间戳加盐的方式去用MD5或者其他的加密方式生成一个key。将这些字符传到后台以后后台接收这些参数,然后以特定的格式在后台再以同样的公式生成key,与前台传过来的key进行比对。
2。前台传的所有值当成一个键值对的map,然后在map中加入一些随机参数,用MD5或者其他的加密方式生成一个key。后台以同样的方式生成key去匹配前台传过来的校验字符串。
代码一般需要做到简洁,所以在验证方面我们肯定希望在代码的同一个地方去做处理校验,因此我想到了拦截器,去拦截所有请求,获取所有参数,在拦截器中进行校验操作。但是拦截器有一个弊端:
request.getParameterMap() // 只可以获取url地址上的参数
比如 当前的请求地址为 http://localhost/test?a=1&b=2。通过拦截器是可以截取到当前的参数的。
但是当我们的参数不在url地址上,也就是说如果 前端发来一个POST 请求,参数是以 body 形式传来的,这时候用拦截器是无法截取到请求的参数,上述代码中返回的是一个空的HashMap。
拦截器的具体实现:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
String url = request.getRequestURI();
if(request.getParameterMap().size() > 0){
// 拦截URl参数请求,在切面拦截BODY请求参数
Map<String,String[]> map = request.getParameterMap();
Map<String,Object> signMap = new HashMap<>();
for(Map.Entry<String,String[]> entry:map.entrySet()){
if(!entry.getKey().equals("sign")){
signMap.put(entry.getKey(),entry.getValue());
}
}
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] str = md5.digest((signMap.toString()).getBytes());
if(map.get("sign") != null && !CommonUtil.byteArrayToHexString(str).equals(Arrays.toString(map.get("sign")))){
returnJson(response, "校验参数有误");
return false;
}
}
if(!isWhiteUrl(url)) {
if (StringUtils.isBlank(token)) {
returnJson(response, "token不能为空");
return false;
}
if (userMapper.selectByPrimaryToken(null, token) == null) {
returnJson(response, "未找到用户");
return false;
}
}
return true;
}
protected void returnJson(HttpServletResponse response,String json){
PrintWriter writer = null;
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=utf-8");
try{
writer = response.getWriter();
Map<String,Object> map = new HashMap<>();
map.put("data",new HashMap<>());
map.put("err_code",0);
map.put("reason",json);
writer.print(JSONUtils.toJSONString(map));
}catch (IOException e){
e.printStackTrace();
}finally {
if(writer != null ){
writer.close();
}
}
}
那么,如何在 POST 请求中截取 body 参数呢?
查阅了大量的资料后,网上有一种方式是新建一个类去重写 HttpServletRequestWrapper 类,重写他的所有方法。作者实验了这种方式,但是一直在代码中会报 流关闭 的异常,一直未能解决该异常,所以我想了第二种方式。
因为如果参数在URL上我是可以获取的,仅仅是要适配post请求的参数,所以这个是具体到了方法,因此我想到可以通过AOP的方式去拦截参数,而又不是拦截所有的方法,因此自定义一个注解去标记需要拦截的方法
自定义的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface PostVerify{
}
AOP具体的实现
@Pointcut("execution(public * com.app.stock.controller.*.*(..))")
public void checkRequest() {
}
@Pointcut("@annotation(com.app.stock.annoation.PostVerify)")
public void annoationPoint(){};
@Before("checkRequest() && annoationPoint()")
public void before(JoinPoint joinPoint) throws Exception {
Map<String, Object> map;
if(joinPoint.getArgs()[0] instanceof Map){
map = (Map) joinPoint.getArgs()[0];
}else{
map = CommonUtil.beanToMap(joinPoint.getArgs()[0]);
}
Map<String,Object> signMap = new LinkedHashMap<>();
for(Map.Entry<String,Object> entry:map.entrySet()){
if(!entry.getKey().equals("sign")){
signMap.put(entry.getKey(),entry.getValue());
}
}
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] str = md5.digest((signMap.toString()).getBytes());
if(map.get("sign") != null && !CommonUtil.byteArrayToHexString(str).equals(map.get("sign").toString())){
// 抛出异常,去上一层截取
throw new VerifyException("");
}
}
@Around("checkRequest()")
public Response around(ProceedingJoinPoint pjp) throws Throwable {
Response response = new Response();
response.setReason("操作成功");
response.setData(new HashMap<>());
try{
response = (Response) pjp.proceed();
}catch (VerifyException e){
response.setReason("校验失败");
}catch (Exception e){
logger.error("异常信息",e);
response.setReason("系统异常");
}
return response;
}
因为获取到参数时在@Before中获取的,因此在 @Before中加入限制条件
@Before("checkRequest() && annoationPoint()")
只有满足这两个条件的才会走 before,否则只会进入 AOP 的 Around,当在@before中作校验时,如果校验的字符串和后台生成的字符串不相同,则向上抛出一个自定义的异常,在 Around 中 去catch 到 before 中抛出的异常,然后返回给前端提示信息 "校验失败" 即可。
如果校验失败的话即返回
{
"data": "{}",
"reason": "校验失败",
"error_code": 0
}
总结:
1。 用继承的方式也可以很好的解决拦截body请求的问题(可能是要搭配过滤器一起使用)
2。如果用AOP的方式去实现的话也可以很好的解决问题,在AOP中可以做更好的自适应配置,如果用我的方法的话记得在对应的controller的方法上加上自定义注解
3。如果不嫌麻烦的话可以写一个共通的方法,去用反射的方式进行验证,然后在每个方法中进行引用即可。