1、增加过滤器, copy一个requet流(附件上传不做签名认证,仍然用之前的流)
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Component
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException, IOException {
ServletRequest requestWrapper = null;
String header = ((HttpServletRequest)servletRequest).getHeader("Content-Type");
if (header != null && "multipart/form-data".equals(header.split(";")[0])) {
System.out.println("附件类型");
filterChain.doFilter(servletRequest, servletResponse);
} else {
requestWrapper = new MyHttpServletRequestWrapper((HttpServletRequest)servletRequest);
filterChain.doFilter(requestWrapper, servletResponse);
}
}
@Override
public void destroy() {}
}
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final String body;
public MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
InputStream is = null;
StringBuilder sb = null;
try {
is = request.getInputStream();
sb = new StringBuilder();
String content="";
BufferedReader bufferedReader =new BufferedReader(new InputStreamReader(is, "utf-8"));
while(( content = bufferedReader.readLine()) !=null){
sb.append(content);
}
} finally {
if (is != null) {
is.close();
}
}
body = sb.toString();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());
return 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 bais.read();
}
/*@Override
public void close() throws IOException {
System.out.println(1);
bais.close();
}*/
};
}
public String getBody() {
return body;
}
}
2 签名拦截器配置
配置几个未登录前的接口不做签名认证
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 签名 拦截器配置
*/
@Configuration
public class SignAuthConfiguration implements WebMvcConfigurer {
public static String[] urlList = new String[] {"/sys/dict/getDictItems/*", "/sys/dict/loadDict/*",
"/sys/dict/loadDictOrderByValue/*", "/sys/dict/loadDictItem/*", "/sys/dict/loadTreeData",
"/sys/api/queryTableDictItemsByCode", "/sys/api/queryFilterTableDictInfo", "/sys/api/queryTableDictByKeys",
"/sys/api/translateDictFromTable", "/sys/api/translateDictFromTableByKeys"};
public static String[] excludeList = new String[]{"/sys/logout","/sys/phoneLogin","/sys/phoneLogin","/sys/login",
"/account/sms","/auth/forge-password","/auth/register","/sys/common/upload","/sys/common/static/**","/sys/mLogin"};
@Bean
public SignAuthInterceptor signAuthInterceptor() {
return new SignAuthInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
//registry.addInterceptor(signAuthInterceptor()).addPathPatterns(urlList);
registry.addInterceptor(signAuthInterceptor()).addPathPatterns("/**").excludePathPatterns(excludeList);
}
}
附件上传、调用远程文件链接等不做签名认证
import java.io.PrintWriter;
import java.util.SortedMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.util.DateUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.sign.util.HttpUtils;
import org.jeecg.config.sign.util.SignUtil;
import org.springframework.web.servlet.HandlerInterceptor;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
/**
* 签名拦截器
* @author qinfeng
*/
@Slf4j
public class SignAuthInterceptor implements HandlerInterceptor {
/**
* 5分钟有效期
*/
private final static long MAX_EXPIRE = 5 * 60;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String method = request.getMethod();
log.error("request URI = " + request.getRequestURI());
String header = ((HttpServletRequest)request).getHeader("Content-Type");
if (header != null && "multipart/form-data".equals(header.split(";")[0])) {
//文件上传不做签名认证
return true;
}
//直接链接,如打开pdf文件等
String uri = request.getRequestURI();
if(uri.lastIndexOf("/")>-1) {
String fileName = uri.substring(uri.lastIndexOf("/")+1) ;
if(fileName.indexOf(".")>-1){
Pattern pattern = Pattern.compile("^[\\w\\s\\.\\-]+(?<!\\.\\.)$");
Matcher matcher = pattern.matcher(fileName);
if (matcher.matches()) {
return true;
}
}
}
//图片
if(uri.indexOf("data:image")>-1){
return true ;
}
HttpServletRequest requestWrapper = new HttpServletRequestWrapper(request);
//获取全部参数(包括URL和body上的)
SortedMap<String, String> allParams = HttpUtils.getAllParams(requestWrapper);
//对参数进行签名验证
String headerSign = request.getHeader(CommonConstant.X_SIGN);
String timesTamp = request.getHeader(CommonConstant.X_TIMESTAMP);
if (oConvertUtils.isEmpty(headerSign)) {
headerSign = request.getParameter(CommonConstant.X_SIGN);
}
if (oConvertUtils.isEmpty(timesTamp)) {
timesTamp = request.getParameter(CommonConstant.X_TIMESTAMP);
}
//1.校验时间有消息
try {
DateUtils.parseDate(timesTamp, "yyyyMMddHHmmss");
} catch (Exception e) {
throw new IllegalArgumentException("签名验证失败:X-TIMESTAMP格式必须为:yyyyMMddHHmmss");
}
Long clientTimestamp = Long.parseLong(timesTamp);
//判断时间戳 timestamp=201808091113
if ((DateUtils.getCurrentTimestamp() - clientTimestamp) > MAX_EXPIRE) {
throw new IllegalArgumentException("签名验证失败:X-TIMESTAMP已过期");
}
//2.校验签名
boolean isSigned = SignUtil.verifySign(allParams,headerSign);
if (isSigned) {
log.debug("Sign 签名通过!Header Sign : {}",headerSign);
return true;
} else {
log.error("request URI = " + request.getRequestURI());
log.error("Sign 签名校验失败!Header Sign : {}",headerSign);
//校验失败返回前端
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter out = response.getWriter();
Result<?> result = Result.error("Sign签名校验失败!");
out.print(JSON.toJSON(result));
return false;
}
}
}
支持提交null数据
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.config.StaticConfig;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.util.*;
/**
* 签名工具类
*
* @author jeecg
* @date 20210621
*/
@Slf4j
public class SignUtil {
public static final String xPathVariable = "x-path-variable";
/**
* @param params
* 所有的请求参数都会在这里进行排序加密
* @return 验证签名结果
*/
public static boolean verifySign(SortedMap<String, String> params,String headerSign) {
if (params == null || StringUtils.isEmpty(headerSign)) {
return false;
}
// 把参数加密
String paramsSign = getParamsSign(params);
log.error("Param Sign : {}", paramsSign);
boolean result = !StringUtils.isEmpty(paramsSign) && headerSign.equals(paramsSign);
if(!result){
log.error("参数签名:"+paramsSign+ " 传递的签名:"+headerSign);
}
return result ;
}
/**
* @param params
* 所有的请求参数都会在这里进行排序加密
* @return 得到签名
*/
public static String getParamsSign(SortedMap<String, String> params) {
//去掉 Url 里的时间戳
params.remove("_t");
String paramsJsonStr = JSONObject.toJSONString(params, SerializerFeature.WriteMapNullValue);
log.error("Param paramsJsonStr : {}", paramsJsonStr);
StaticConfig staticConfig = SpringContextUtils.getBean(StaticConfig.class);
String signatureSecret = staticConfig.getSignatureSecret();
if(oConvertUtils.isEmpty(signatureSecret) || signatureSecret.contains("${")){
throw new JeecgBootException("签名密钥 ${jeecg.signatureSecret} 缺少配置 !!");
}
return DigestUtils.md5DigestAsHex((paramsJsonStr + signatureSecret).getBytes()).toUpperCase();
}
}
字符转换null转换’null’ bug问题
public class HttpUtils {
public static SortedMap<String, String> getAllParams(HttpServletRequest request) throws IOException {
SortedMap<String, String> result = new TreeMap<>();
// 获取URL上最后带逗号的参数变量 sys/dict/getDictItems/sys_user,realname,username
String pathVariable = request.getRequestURI().substring(request.getRequestURI().lastIndexOf("/") + 1);
if (pathVariable.contains(",")) {
log.info(" pathVariable: {}",pathVariable);
String deString = URLDecoder.decode(pathVariable, "UTF-8");
log.info(" pathVariable decode: {}",deString);
result.put(SignUtil.xPathVariable, deString);
}
// 获取URL上的参数
Map<String, String> urlParams = getUrlParams(request);
for (Map.Entry entry : urlParams.entrySet()) {
result.put((String)entry.getKey(), (String)entry.getValue());
}
Map<String, String> allRequestParam = new HashMap<>(16);
// get请求不需要拿body参数
if (!HttpMethod.GET.name().equals(request.getMethod())) {
allRequestParam = getAllRequestParam(request);
}
// 将URL的参数和body参数进行合并
if (allRequestParam != null) {
for (Map.Entry entry : allRequestParam.entrySet()) {
//空数据不做字符转换
result.put((String)entry.getKey(), entry.getValue()==null?null:String.valueOf(entry.getValue()));
}
}
return result;
}
...
}
前端修改
1、增删修查 增加签名
2、查询时去掉null值(因为get方法提交url自动会去掉为空的参数),再签名。解决带null签名与后台签名不一致问题。
//post
export function postAction(url,parameter) {
let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳,添加在请求接口 Header
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()};
return axios({
url: url,
method:'post' ,
data: parameter,
headers: signHeader
})
}
//post method= {post | put}
export function httpAction(url,parameter,method) {
let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳,添加在请求接口 Header
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()};
return axios({
url: url,
method:method ,
data: parameter,
headers: signHeader
})
}
//put
export function putAction(url,parameter) {
let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳,添加在请求接口 Header
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()};
return axios({
url: url,
method:'put',
data: parameter,
headers: signHeader
})
}
//get
export function getAction(url,parameter) {
//此方法get请求去掉了null,但签名用了null数据
//解决此办法,在签名前去掉null数据
let newParameter = {}
for (let key in parameter) {
if (parameter[key] !== null) {
newParameter[key] = parameter[key]
}
}
let sign = signMd5Utils.getSign(url, newParameter);
//将签名和时间戳,添加在请求接口 Header
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()};
return axios({
url: url,
method: 'get',
params: newParameter,
headers: signHeader
})
}
//deleteAction
export function deleteAction(url,parameter) {
let sign = signMd5Utils.getSign(url, parameter);
//将签名和时间戳,添加在请求接口 Header
let signHeader = {"X-Sign": sign,"X-TIMESTAMP": signMd5Utils.getDateTimeToString()};
return axios({
url: url,
method: 'delete',
params: parameter,
headers: signHeader
})
}