需求描述:前端发起请求的参数携带sign=xxxx,后台验证签名是够正确
sign签名生成规则:
1.将post请求的body转成jsonstring (按照body里key的自然升序排列),得到stringA,即: “reqBody=
j
s
o
n
s
t
r
i
n
g
"
2.
对
字
符
串
s
t
r
i
n
g
A
=
"
r
e
q
B
o
d
y
=
{jsonstring}" 2.对字符串stringA="reqBody=
jsonstring"2.对字符串stringA="reqBody={jsonstring}”, stringB=“cpToken=
c
p
T
o
k
e
n
"
,
s
t
r
i
n
g
C
=
"
o
e
m
=
{cpToken}", stringC="oem=
cpToken",stringC="oem={oem}”, stringD=“oemId=
o
e
m
I
d
"
,
=
s
t
r
i
n
g
E
=
"
t
i
m
e
s
t
a
m
p
=
{oemId}",=stringE="timestamp=
oemId",=stringE="timestamp={timestamp}”, stringF=“source=${source}”,使用按照字符串的字母升序(a→z)进行排列,获得字符串stringG;、
3.stringG后拼接“&secretKey=Onlineradio”得到StringH
4.对stringH进行BASE64编码得到字符串stringI;
5.对stringI进行MD5运算,并将得到的字符串所有字符转换为大写,得到sign值。
项目结构
config类
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @Author: 李浩真
*/
public class config implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截
InterceptorRegistration registration = registry.addInterceptor(new HttpServletRequestReplacedInterceptor());
registration.excludePathPatterns("/error");
registration.addPathPatterns("/v1/**");
registry.addInterceptor(new HttpServletRequestReplacedInterceptor()).addPathPatterns("/{version}/**");
}
@Bean
public FilterRegistrationBean httpServletRequestReplacedRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new HttpServletRequestReplacedFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter("paramName", "paramValue");
registration.setName("httpServletRequestReplacedFilter");
registration.setOrder(1);
return registration;
}
}
HttpServletRequestReplacedFilter类
/**
* 李浩真
*/
@Component
public class HttpServletRequestReplacedFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if(request instanceof HttpServletRequest) {
requestWrapper = new RequestReaderHttpServletRequestWrapper((HttpServletRequest) request);
}
//获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
// 在chain.doFiler方法中传递新的request对象
if(requestWrapper == null) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
HttpServletRequestReplacedInterceptor
/**
* @author 李浩真
*/
@Component
public class HttpServletRequestReplacedInterceptor extends HandlerInterceptorAdapter {
// 进入控制器之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断请求类型
if (request.getMethod().equals("GET")) {
return doGet(request, response);
} else if (request.getMethod().equals("POST")) {
return doPost(request, response);
}
return false;
}
/**
* GET请求
*/
public Boolean doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
//从request获取到所有的参数及其值
String sign = request.getParameter("sign");
if (sign == null) {
returnCode(request, response, sign);
return false;
}
Enumeration<?> pNames = request.getParameterNames();
Map<String, Object> map = new HashMap<String, Object>();
while (pNames.hasMoreElements()) {
String pName = (String) pNames.nextElement();
Object pValue = request.getParameter(pName);
map.put(pName, pValue);
}
//移除
map.remove("sign");
String newSign = SignUtil.createSign(map);
if (!newSign.equals(sign)) {
returnCode(request, response, sign);
}
return true;
}
/**
* POST请求
*/
public Boolean doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
String bodyString = HttpHelper.getBodyString(request);
net.sf.json.JSONObject jb = net.sf.json.JSONObject.fromObject(bodyString);
Map<String, Object> map = (Map<String, Object>) jb;
String sign = (String) map.get("sign");
//移除
map.remove("sign");
String newSign = SignUtil.createSign(map);
if (!newSign.equals(sign)) {
returnCode(request, response, sign);
}
return true;
}
/**
* 验证签名方法:失败,返回false
*/
public boolean returnCode(HttpServletRequest request, HttpServletResponse response, String sign) throws IOException {
OutputStream out = null;
try {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
net.sf.json.JSONObject res = new JSONObject();
res.put("success", false);
// res.put("status code", CommonErrorEnum.SIGNATURE_VERIFICATION_FAILED.getValue());
// res.put("message", CommonErrorEnum.SIGNATURE_VERIFICATION_FAILED.getShowMessage());
res.put("status code", "123321");
res.put("message", "验签失败");
out = response.getOutputStream();
out.write(res.toString().getBytes());
} catch (IOException e) {
e.printStackTrace();
} finally {
out.flush();
out.close();
}
return false;
}
}
RequestReaderHttpServletRequestWrapper
import com.example.demo.util.HttpHelper;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
public class RequestReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final byte[] body;
public RequestReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
body = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8"));
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
HttpHelper
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
/**
* @author 李浩真
*/
public class HttpHelper {
public static String getBodyString(HttpServletRequest request) throws IOException {
StringBuilder sb = new StringBuilder();
InputStream inputStream = null;
BufferedReader reader = null;
try {
inputStream = request.getInputStream();
reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
String line = "";
while ((line = reader.readLine()) != null) {
sb.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return sb.toString();
}
}
MD5Util
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* @author 李浩真
* MD5工具
*/
public class MD5Util {
/**
* 16进制的字符串数组
*/
private final static String[] HEX_DIGITS_STRINGS = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d",
"e", "f"};
/**
* 16进制的字符集
*/
private final static char[] HEX_DIGITS_CHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'A', 'B', 'C', 'D', 'E', 'F'};
/**
* MD5加密字符串
*
* @param source 源字符串
* @return 加密后的字符串
*/
public static String getMD5(String source) {
String mdString = null;
if (source != null) {
try {
mdString = getMD5(source.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
return mdString;
}
/**
* MD5加密以byte数组表示的字符串
*
* @param source 源字节数组
* @return 加密后的字符串
*/
public static String getMD5(byte[] source) {
String s = null;
final int temp = 0xf;
final int arraySize = 32;
final int strLen = 16;
final int offset = 4;
try {
MessageDigest md = MessageDigest
.getInstance("MD5");
md.update(source);
byte[] tmp = md.digest();
char[] str = new char[arraySize];
int k = 0;
for (int i = 0; i < strLen; i++) {
byte byte0 = tmp[i];
str[k++] = HEX_DIGITS_CHAR[byte0 >>> offset & temp];
str[k++] = HEX_DIGITS_CHAR[byte0 & temp];
}
s = new String(str);
} catch (Exception e) {
e.printStackTrace();
}
return s;
}
/**
* @param plainText 明文
* @return 32位密文
*/
public static String getMD5New(String plainText) {
String re_md5 = new String();
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(plainText.getBytes());
byte b[] = md.digest();
int i;
StringBuffer buf = new StringBuffer("");
for (int offset = 0; offset < b.length; offset++) {
i = b[offset];
if (i < 0) {
i += 256;
}
if (i < 16) {
buf.append("0");
}
buf.append(Integer.toHexString(i));
}
re_md5 = buf.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return re_md5;
}
/**
* * 获取文件的MD5值
*
* @param file 目标文件
* @return MD5字符串
* @throws Exception
*/
public static String getFileMD5String(File file) throws Exception {
String ret = "";
FileInputStream in = null;
FileChannel ch = null;
try {
in = new FileInputStream(file);
ch = in.getChannel();
ByteBuffer byteBuffer = ch.map(FileChannel.MapMode.READ_ONLY, 0,
file.length());
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
messageDigest.update(byteBuffer);
ret = byteArrayToHexString(messageDigest.digest());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ch != null) {
try {
ch.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return ret;
}
/**
* * 获取文件的MD5值
*
* @param fileName 目标文件的完整名称
* @return MD5字符串
* @throws Exception
*/
public static String getFileMD5String(String fileName) throws Exception {
return getFileMD5String(new File(fileName));
}
/**
* 加密
*
* @param source 需要加密的原字符串
* @param encoding 指定编码类型
* @param uppercase 是否转为大写字符串
* @return
*/
public static String MD5Encode(String source, String encoding, boolean uppercase) {
String result = null;
try {
result = source;
// 获得MD5摘要对象
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
// 使用指定的字节数组更新摘要信息
messageDigest.update(result.getBytes(encoding));
// messageDigest.digest()获得16位长度
result = byteArrayToHexString(messageDigest.digest());
} catch (Exception e) {
e.printStackTrace();
}
return uppercase ? result.toUpperCase() : result;
}
/**
* 转换字节数组为16进制字符串
*
* @param bytes 字节数组
* @return
*/
private static String byteArrayToHexString(byte[] bytes) {
StringBuilder stringBuilder = new StringBuilder();
for (byte tem : bytes) {
stringBuilder.append(byteToHexString(tem));
}
return stringBuilder.toString();
}
/**
* * 将字节数组中指定区间的子数组转换成16进制字符串
*
* @param bytes 目标字节数组
* @param start 起始位置(包括该位置)
* @param end 结束位置(不包括该位置)
* @return 转换结果
*/
public static String bytesToHex(byte bytes[], int start, int end) {
StringBuilder sb = new StringBuilder();
for (int i = start; i < start + end; i++) {
sb.append(byteToHexString(bytes[i]));
}
return sb.toString();
}
/**
* 转换byte到16进制
*
* @param b 要转换的byte
* @return 16进制对应的字符
*/
private static String byteToHexString(byte b) {
int n = b;
if (n < 0) {
n = 256 + n;
}
int d1 = n / 16;
int d2 = n % 16;
return HEX_DIGITS_STRINGS[d1] + HEX_DIGITS_STRINGS[d2];
}
/**
* * 校验密码与其MD5是否一致
*
* @param pwd 密码字符串
* @param md5 基准MD5值
* @return 检验结果
*/
public static boolean checkPassword(String pwd, String md5) {
return getMD5(pwd).equalsIgnoreCase(md5);
}
/**
* * 校验密码与其MD5是否一致
*
* @param pwd 以字符数组表示的密码
* @param md5 基准MD5值
* @return 检验结果
*/
public static boolean checkPassword(char[] pwd, String md5) {
return checkPassword(new String(pwd), md5);
}
/**
* * 检验文件的MD5值
* @param file 目标文件
* @param md5 基准MD5值
* @return 检验结果
* @throws Exception
*/
public static boolean checkFileMD5(File file, String md5) throws Exception {
return getFileMD5String(file).equalsIgnoreCase(md5);
}
/**
* * 检验文件的MD5值
* @param fileName 目标文件的完整名称
* @param md5 基准MD5值
* @return 检验结果
* @throws Exception
*/
public static boolean checkFileMD5(String fileName, String md5) throws Exception {
return checkFileMD5(new File(fileName), md5);
}
}
SignUtil
/**
* @author 李浩真
* 签名与验签工具
*/
@Slf4j
public class SignUtil {
public static String createSign(Map<String, Object> originMap) {
if (originMap == null) {
return null;
}
originMap = sortMapByKey(originMap);
StringBuffer originStr = new StringBuffer();
for (Map.Entry<String, Object> entry : originMap.entrySet()) {
originStr.append(entry.getKey() + "=" + entry.getValue());
originStr.append("&");
}
originStr = originStr.append("secretKey=ProjectName");
return MD5Util.getMD5(Base64.getEncoder().encodeToString(originStr.toString().getBytes(StandardCharsets.UTF_8)));
}
public static Map<String, Object> sortMapByKey(Map<String, Object> map) {
/**
* 对Map对象的key升序(a->z)排列
*/
if (map == null || map.isEmpty()) {
return null;
}
Map<String, Object> sortMap = new TreeMap<>(Comparator.naturalOrder());
sortMap.putAll(map);
return sortMap;
}
}