本文实现了接口请求签名校验,时间戳判断,响应数据返回签名等内容。
这个签名校验,和返回签名可以用多种方法实现。
第一种aop 方式实现
自定义注解体
/** * @author xxx */ @Retention(value = RetentionPolicy.RUNTIME) public @interface SignatureValidation { }
aop实现
/** * @author xxx */ @Aspect @Component public class SignatureValidation { /** * 时间戳请求最小限制(600s) * 设置的越小,安全系数越高,但是要注意一定的容错性 */ private static final long MAX_REQUEST = 10 * 60 * 1000L; /** * 秘钥 */ private static final String SECRET= "test"; /** * 验签切点(完整的找到设置的文件地址) */ @Pointcut("execution(@com.xx.xxx.xxxxx.aop.SignatureValidation * *(..))") private void verifyUserKey() { } /** * 获取请求数据,并校验签名数据 */ @Before("verifyUserKey()") public void doBasicProfiling(JoinPoint point) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); String sign = "" ; String timestamp = "" ; String version = ""; SortedMapsortedMap = new TreeMap (); for ( Object obj :point.getArgs()) { JSONObject jsonObject =JSONUtil.parseObj(obj); if( !StrUtil.isEmptyIfStr(jsonObject.get("sign"))){ sign=jsonObject.get("sign").toString(); } if(!StrUtil.isEmptyIfStr(jsonObject.get("timestamp"))){ timestamp=jsonObject.get("timestamp").toString(); sortedMap.put("timestamp", timestamp); } if(!StrUtil.isEmptyIfStr(jsonObject.get("version"))){ version=jsonObject.get("version").toString(); sortedMap.put("version", version); } if(!StrUtil.isEmptyIfStr(jsonObject.get("data"))){ String dataStr= jsonObject.get("data").toString(); if(!JSONUtil.isJsonObj(dataStr)) { if(JSONUtil.isJsonArray(dataStr)){ sortedMap.put("data", JSONUtil.parseArray(dataStr).toString()); } sortedMap.put("data", dataStr); } else { JSONObject dataJson= JSONUtil.parseObj(dataStr); @SuppressWarnings("unchecked") Set keySet = dataJson.keySet(); String key = ""; Object value = null; // 遍历json数据,添加到SortedMap对象 for (Iterator iterator = keySet.iterator(); iterator.hasNext();) { key = iterator.next(); value = dataJson.get(key); String valueStr=""; if(!StrUtil.isEmptyIfStr(value)){ valueStr=value.toString(); } sortedMap.put(key, valueStr); } } } } if (StrUtil.isEmptyIfStr(sign)) { throw new CustomException(BaseResultInfoEnum.ERROR_MISSING_SIGN_1009.getMsg(),BaseResultInfoEnum.ERROR_MISSING_SIGN_1009.getCode()); } //新的签名 String newSign = createSign(sortedMap,SECRET); if (!newSign.equals(sign.toUpperCase())) { throw new CustomException(BaseResultInfoEnum.ERROR_SIGN_1010.getMsg(),BaseResultInfoEnum.ERROR_SIGN_1010.getCode()); } } /** * * @param point * @param responseObject 返回参数 */ @AfterReturning(pointcut="verifyUserKey()",returning="responseObject") public void afterReturning(JoinPoint point,Object responseObject) { HttpServletResponse response=((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse(); if(responseObject instanceof ResponseModel){ ResponseModel responseModel= (ResponseModel) responseObject; responseModel.setTimestamp(System.currentTimeMillis()); responseModel.setVersion(0); String sign= Md5Utils.createSign(Md5Utils.createParameters(responseModel),SECRET); responseModel.setSign(sign); } } }
md5签名
/** * @author xxx */ public class Md5Utils { /** * 生成签名 * @param parameters * @param key 商户ID * @return */ public static String createSign(SortedMapparameters, String key){ StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet(); Iterator it = es.iterator(); while(it.hasNext()) { Map.Entry entry = (Map.Entry)it.next(); String k = (String)entry.getKey(); Object v = entry.getValue(); if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + key); String sign = SecureUtil.md5(sb.toString()).toUpperCase(); return sign; } /** * 签名参数 * @param responseModel 响应数据签名返回给调用者 * @return */ public static SortedMap createParameters(ResponseModel responseModel){ SortedMap sortedMap = new TreeMap (); if(responseModel!=null) { sortedMap.put("timestamp", Convert.toStr(responseModel.getTimestamp()) ); sortedMap.put("version", Convert.toStr(responseModel.getVersion())); JSONObject json = JSONUtil.parseObj(responseModel, false); if(responseModel.getData()!=null) { sortedMap.put("data", json.get("data").toString()); } } return sortedMap; } }
使用,在控制中的方法上方注解即可
第二种拦截器 这里只做了时间判断,签名校验可以根据需要修改即可实现。
过滤器
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * @author xx */ @WebFilter(urlPatterns = "/*",filterName = "channelFilter") public class ChannelFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ServletRequest requestWrapper = null; if(servletRequest instanceof HttpServletRequest) { requestWrapper = new RequestWrapper((HttpServletRequest) servletRequest); } if(requestWrapper == null) { filterChain.doFilter(servletRequest, servletResponse); } else { filterChain.doFilter(requestWrapper, servletResponse); } } @Override public void destroy() { } }
拦截器配置
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author xxxx */ @Configuration public class InterceptorConfig implements WebMvcConfigurer{ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(getHandlerInterceptor()); } @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedHeaders("Content-Type", "x-requested-with", "X-Custom-Header") .allowedMethods("PUT", "POST", "GET", "DELETE", "OPTIONS") .allowedOrigins("*") .allowCredentials(true); } @Bean public FilterRegistrationBean repeatedlyReadFilter() { FilterRegistrationBean registration = new FilterRegistrationBean(); ChannelFilter repeatedlyReadFilter = new ChannelFilter(); registration.setFilter(repeatedlyReadFilter); registration.addUrlPatterns("/*"); return registration; } @Bean public HandlerInterceptor getHandlerInterceptor() { return new TimestampInterceptor(); } }
RequestWrapper 请求流重写处理
import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; /** * @author xxx */ public class RequestWrapper extends HttpServletRequestWrapper { private final String body; public RequestWrapper(HttpServletRequest request) { super(request); StringBuilder stringBuilder = new StringBuilder(); BufferedReader bufferedReader = null; InputStream inputStream = null; try { inputStream = request.getInputStream(); if (inputStream != null) { bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); char[] charBuffer = new char[128]; int bytesRead = -1; while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { stringBuilder.append(charBuffer, 0, bytesRead); } } else { stringBuilder.append(""); } } catch (IOException ex) { } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); } } if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } body = stringBuilder.toString(); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); ServletInputStream servletInputStream = new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } @Override public int read() throws IOException { return byteArrayInputStream.read(); } }; return servletInputStream; } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(this.getInputStream())); } public String getBody() { return this.body; } }
拦截器 这里可以实现请求来的签名处理,这里只处理时间了
import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import xxx.xxx.xxx.common.result.BaseResultInfoEnum; import xxx.xxx.common.core.exception.CustomException; import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author xxx */ public class TimestampInterceptor implements HandlerInterceptor { /** * 时间戳请求最小限制(600s) * 设置的越小,安全系数越高,但是要注意一定的容错性 */ private static final long MAX_REQUEST = 10 * 60 * 1000L; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; if (handlerMethod.getBean() instanceof BasicErrorController) { return true; } // if ("GET".equals(request.getMethod())) { // return true; // } ValidateResponse validateResponse = new ValidateResponse(true, null); RequestWrapper myRequestWrapper = new RequestWrapper((HttpServletRequest) request); validateResponse= checkTimestamp(myRequestWrapper.getBody()); if (!validateResponse.isValidate()) { throw validateResponse.getException(); } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { } private ValidateResponse checkTimestamp(String requestBody) { try { JSONObject jsonObject = JSONUtil.parseObj(requestBody); String timestamp = "" ; if(!StrUtil.isEmptyIfStr(jsonObject.get("timestamp"))) { timestamp=jsonObject.get("timestamp").toString(); } if (StrUtil.isEmptyIfStr(timestamp)) { return new ValidateResponse(false, new CustomException(BaseResultInfoEnum.ERROR_MISSING_TIMESTAMP_1007.getMsg(), BaseResultInfoEnum.ERROR_MISSING_TIMESTAMP_1007.getCode())); } long now = System.currentTimeMillis(); long time = Long.parseLong(timestamp); if (now - time > MAX_REQUEST) { return new ValidateResponse(false, new CustomException(BaseResultInfoEnum.ERROR_TIMESTAMP_TIMEOUT_1008.getMsg(), BaseResultInfoEnum.ERROR_TIMESTAMP_TIMEOUT_1008.getCode())); } } catch (Exception e) { e.printStackTrace(); } return new ValidateResponse(true, null); } /** * 校验返回对象 */ private static class ValidateResponse { private boolean validate; private CustomException exception; public ValidateResponse(boolean validate, CustomException exception) { this.validate = validate; this.exception = exception; } public boolean isValidate() { return validate; } public Exception getException() { return exception; } } }
返回给前端(或其他平台的)处理类
import comzzzz.xx.common.pojo.PageResponseModel; import com.zzz.xxxx.common.pojo.ResponseModel; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; /** * @author sunrh */ @ControllerAdvice public class ResponseBodyTimestamp implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter methodParameter, Class aClass) { return true; } @Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { //就是这里处理返回签名数据 if(o instanceof ResponseModel){ ResponseModel responseModel= (ResponseModel) o; responseModel.setTimestamp(System.currentTimeMillis()); return responseModel; } if(o instanceof PageResponseModel){ PageResponseModel responseModel= (PageResponseModel) o; responseModel.setTimestamp(System.currentTimeMillis()); return responseModel; } return o; } }
最终实现的效果图