接口验签
1、过滤器
为啥直接通过拦截器无法实现,因为request的输入流只能读取一次因为流对应的是数据,数据放在内存中,有的是部分放在内存中。read 一次标记一次当前位置(mark position),第二次read就从标记位置继续读(从内存中copy)数据。 所以这就是为什么读了一次第二次是空了。 怎么让它不为空呢?只要inputstream 中的pos 变成0就可以重写读取当前内存中的数据。javaAPI中有一个方法public void reset() 这个方法就是可以重置pos为起始位置,但是不是所有的IO读取流都可以调用该方法!ServletInputStream是不能调用reset方法,这就导致了只能调用一次getInputStream()。
因此通过过滤器里面进行一层包装,包装拿到了request中的请求数据,并且原来的request继续往后传递,可以理解成做了一个拷贝。
HttpServletRequestReplacedFilter
package com.yzf.enterprise.open.platform.bff.security.sign.filter;
import com.yzf.enterprise.open.platform.bff.security.sign.wrapper.RequestWrapper;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
public class HttpServletRequestReplacedFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if(request instanceof HttpServletRequest) {
requestWrapper = new RequestWrapper((HttpServletRequest) request);
}
//获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
// 在chain.doFiler方法中传递新的request对象
if(requestWrapper == null) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
@Override
public void destroy() {
}
}
RequestWrapper
package com.yzf.enterprise.open.platform.bff.security.sign.wrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
/**
* 由于request数据读取一次就无法读取,因此通过包装类来解决
*/
public class RequestWrapper extends HttpServletRequestWrapper {
private final String body;
private static final Logger LOGGER = LoggerFactory.getLogger(RequestWrapper.class);
public RequestWrapper(HttpServletRequest request) {
/**
* 由于继承了HttpServletRequestWrapper,HttpServletRequestWrapper又继承了ServletRequestWrapper,ServletRequestWrapper
* 中有一个private ServletRequest request;也就是将原来的request做了一个备份,具体读到的数据放在body中
*/
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 (Exception ex) {
LOGGER.error("过滤器request请求包装时出现异常", ex);
} finally {
if (inputStream != null) {
try {
inputStream.close();
}
catch (IOException e) {
LOGGER.error("过滤器request请求包装关闭流出现异常", e);
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
}
catch (IOException e) {
LOGGER.error("过滤器request请求包装关闭流出现异常", e);
}
}
}
body = stringBuilder.toString();
LOGGER.info("过滤器request请求包装结果为:" + body);
}
@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;
}
}
FilterRegisterConfig
然后将filter注册到容器中-----基于springboot项目
package com.yzf.enterprise.open.platform.bff.security.sign.filter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterRegisterConfig {
@Autowired
private HttpServletRequestReplacedFilter httpServletRequestReplacedFilter;
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(httpServletRequestReplacedFilter);
//拦截所有的请求,给每个请求都包装一下,拦截器中再判断是否需要拦截处理
registrationBean.addUrlPatterns("/*");
//给自定义的filter设置顺序,值越小,优先级越高,建议可以稍微高一些,防止影响框架的一些filter
registrationBean.setOrder(10);
return registrationBean;
}
}
2、拦截器
SignInterceptor
package com.yzf.enterprise.open.platform.bff.security.sign.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.yzf.accounting.common.base.AjaxResult;
import com.yzf.accounting.common.exception.BizRuntimeException;
import com.yzf.enterprise.open.platform.bff.security.sign.wrapper.RequestWrapper;
import com.yzf.enterprise.open.platform.client.api.OpAccessInfoClient;
import com.yzf.enterprise.open.platform.client.dto.OpAccessInfoDto;
import com.yzf.enterprise.open.platform.common.exception.BusinessErrorCode;
import com.yzf.enterprise.open.platform.common.utils.SignUtil;
import com.yzf.enterprise.open.platform.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 统一处理过滤请求:校验签名是否合法、校验时间戳是否超过30s
*/
@Component
@Slf4j
public class SignInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(SignInterceptor.class);
//目标方法执行之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
RequestWrapper requestWrapper;
/**
* 统一处理业务接口验签以及验证时间戳操作
*/
if(request instanceof RequestWrapper) {
requestWrapper = (RequestWrapper) request;
}else {
requestWrapper = new RequestWrapper(request);
}
String requestBody = requestWrapper.getBody();
if(StringUtils.isBlank(requestBody)) {
return true;
}
JSONObject jsonObject = null;
try {
jsonObject = JSONObject.parseObject(requestBody);
logger.info("拦截器中请求参数格式化后为:" + jsonObject.toJSONString());
}catch(Exception ex) {
logger.error("签名时间戳全局拦截器请求消息转化出现异常", ex);
throw new BizRuntimeException(BusinessErrorCode.ERROR_PARAM);
}
Long timestamp = jsonObject.getLong("timestamp");
String sign = jsonObject.getString("sign");
if (timestamp == null || StringUtils.isBlank(sign)) {
throw new BizRuntimeException(BusinessErrorCode.ERROR_PARAM);
}
/**
* 校验时间戳
*/
if (!SignUtil.validateTimestamp(timestamp)) {
throw new BizRuntimeException(BusinessErrorCode.TIMESTAMP_VALID_ERROR);
}
/**
* 校验参数签名
*/
if (!SignUtil.validateSign(jsonObject, "参与签名的秘钥secret", sign)) {
throw new BizRuntimeException(BusinessErrorCode.SIGN_VALID_ERROR);
}
return true;
}
}
CustomMvcConfig
package com.yzf.enterprise.open.platform.bff.security.sign.interceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class CustomMvcConfig extends WebMvcConfigurationSupport {
@Autowired
private SignInterceptor signInterceptor;
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/js/**").addResourceLocations("classpath:/js/");
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(signInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/access/**",
"/getToken",
"/profile/**",
"/swagger-ui.html",
"/swagger-resources/**",
"/webjars/**",
"/*/api-docs",
"/favicon.ico",
"/actuator/**",
"/error");
}
}
SignUtil
package com.yzf.enterprise.open.platform.common.utils;
import com.alibaba.fastjson.JSON;
import com.yzf.enterprise.open.platform.common.annotation.SignIgnore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
/**
* 签名工具类
*/
public class SignUtil {
/**
* 日志
*/
private static final Logger LOGGER = LoggerFactory.getLogger(SignUtil.class);
/**
* 编码
*/
private static final String CHARSET = "utf-8";
/**
* @param parameters
* @param accessSecret
* @param sign
* @return 注:当接口入参为DTO对象时,使用此方法进行验证签名
*/
public static boolean validateSign(Object parameters, String accessSecret, String sign) {
LOGGER.info("validateSign map:{}", JSON.toJSONString(parameters));
boolean flag = false;
if (parameters == null) {
return flag;
}
String mySign = getSignByObj(parameters, accessSecret);
// 验证签名是否一致
if (mySign.equals(sign)) {
flag = true;
}
return flag;
}
/**
* 校验签名timestamp与当前时间是否超过30s
* 注:true未过期;false过期
*
* @param timestamp
* @return
*/
public static boolean validateTimestamp(Long timestamp) {
if (timestamp == null) {
return false;
}
Date date = new Date(timestamp);
return !date.before(new Date(System.currentTimeMillis() - 30000));
}
/**
* 签名算法
*
* @param o 要参与签名的数据对象
* @return 签名,使用sha256
* @throws IllegalAccessException
*/
public static String getSignByObj(Object o, String key) {
String result = "";
try {
ArrayList<String> list = new ArrayList<>();
Class cls = o.getClass();
/**
* 由于class.getDeclaredFields()无法获取父类的字段,因此通过循环的方式获取其所有父类,并排除掉object类字段
*/
while(cls != null && !cls.getName().toLowerCase().equals("java.lang.object")) {
Field[] fields = cls.getDeclaredFields();
for (Field f : fields) {
f.setAccessible(true);
if (f.isAnnotationPresent(SignIgnore.class)) {
continue;
}
if(ObjectUtil.isSimpleTypeOrString(f.getType())) {
if (f.get(o) != null && f.get(o) != "") {
list.add(f.getName() + "=" + f.get(o) + "&");
}
}
}
cls = cls.getSuperclass();
}
int size = list.size();
String[] arrayToSort = list.toArray(new String[size]);
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
sb.append(arrayToSort[i]);
}
result = sb.toString();
result += key;
result = SHA256Util.getSHA256StrJava(result, CHARSET);
} catch (Exception e) {
LOGGER.error("getSignByObject Exception", e);
}
return result;
}
/**
* 验证签名 服务器端使用
*
* @param parametersMap 参数
* @param secretkey 密钥
* @param sign 签名
* @return
*/
public static boolean validateSign(Map<String, Object> parametersMap, String secretkey, String sign) {
LOGGER.info("validateSign map:{}", JSON.toJSONString(parametersMap));
boolean flag = false;
if (parametersMap.isEmpty()) {
return flag;
}
//去除parametersMap中的sign
parametersMap.remove("sign");
String mySign = signRequest(parametersMap, secretkey);
// 验证签名是否一致
if (mySign.equals(sign)) {
flag = true;
}
return flag;
}
/**
* 签名加密 客户端使用
*
* @param parametersMap 参数
* @param secretkey 密钥
* @return
*/
public static String signRequest(Map<String, Object> parametersMap, String secretkey) {
ArrayList<String> list = new ArrayList<>();
for (Map.Entry<String, Object> entry : parametersMap.entrySet()) {
String key = entry.getKey();
Object val = entry.getValue();
if(ObjectUtil.isSimpleTypeOrString(val.getClass())) {
if (null != val && !"".equals(val)) {
list.add(key + "=" + val + "&");
}
}
}
int size = list.size();
String[] arrayToSort = list.toArray(new String[size]);
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
sb.append(arrayToSort[i]);
}
String result = sb.toString();
result += secretkey;
result = SHA256Util.getSHA256StrJava(result, CHARSET);
return result;
}
}


浙公网安备 33010602011771号