springboot 实现Http接口加签、验签操作方法
作者:旅知知
运行环境
jdk8,springboot
业务背景:
服务之间接口调用,通过签名作为安全认证来保证API的安全性。
签名规则:
1、线下分配appid和appKey,
2、秘钥key+当前时间的毫秒+随机字符串 通过加密工具方法(如MD5,AES等)生成一个token,
3、加入timestamp(时间戳),10分钟内数据有效
4、加入signature,所有数据的签名信息。
传参说明:
只对业务所需的参数进行加密,其他参数在头部携带:
GET请求对url的参数进行加密,
POST请求对body体进行加密。
例如:
A服务调用B服务get请求接口: xxx/xx?a=1&b=2
对参数a=1&b=2进行加密得到sign
A服务http请求:
url=xxx/xx?a=1&b=2
header头部携带的参数
timestamp:时间戳
appid:appid
sign:签名
其他说明:
Path:按照path中的顺序将所有value进行拼接
Query:按照key字典序排序,将所有key=value进行拼接
Form:按照key字典序排序,将所有key=value进行拼接
Body:
Json: 按照key字典序排序,将所有key=value进行拼接(例如{"a":"a","c":"c","b":{"e":"e"}} => a=ab=e=ec=c)
代码部分
pom.xml
<dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> <!--hutool --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.10</version> </dependency>
ConfigProperties
一般是接口费提供的。生成规则随意
在这种情况下,devId通常用作身份认证和授权的凭证。接口提供方会将有效的devId存储在数据库中以进行验证。当调用方发送请求时,接口提供方会验证请求中的devId是否有效,并核对数据库中存储的devId列表。如果devId有效,请求被认为是合法的,然后继续进行后续的处理。
使用devId作为凭证可以确保请求方具有被授权访问接口的权限。它可以用于验证调用方的身份和权限,以确保接口的安全性和合法性。
import java.util.HashMap; import java.util.Map; public class ConfigProperties { static Map<String, String> map = null; static { if (map == null) { map = new HashMap<>(); map.put("devId1", "12345555"); map.put("devId2", "56789000"); map.put("devId3", "rt6356sr"); } } public Map<String, String> getInvokeSystemMethodDevMap(String devId) { return map; } }
签名工具类
1.base64Utils
/** * Base64工具类 * * @author */ public final class Base64 { static private final int BASELENGTH = 128; static private final int LOOKUPLENGTH = 64; static private final int TWENTYFOURBITGROUP = 24; static private final int EIGHTBIT = 8; static private final int SIXTEENBIT = 16; static private final int FOURBYTE = 4; static private final int SIGN = -128; static private final char PAD = '='; static final private byte[] base64Alphabet = new byte[BASELENGTH]; static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; static { for (int i = 0; i < BASELENGTH; ++i) { base64Alphabet[i] = -1; } for (int i = 'Z'; i >= 'A'; i--) { base64Alphabet[i] = (byte) (i - 'A'); } for (int i = 'z'; i >= 'a'; i--) { base64Alphabet[i] = (byte) (i - 'a' + 26); } for (int i = '9'; i >= '0'; i--) { base64Alphabet[i] = (byte) (i - '0' + 52); } base64Alphabet['+'] = 62; base64Alphabet['/'] = 63; for (int i = 0; i <= 25; i++) { lookUpBase64Alphabet[i] = (char) ('A' + i); } for (int i = 26, j = 0; i <= 51; i++, j++) { lookUpBase64Alphabet[i] = (char) ('a' + j); } for (int i = 52, j = 0; i <= 61; i++, j++) { lookUpBase64Alphabet[i] = (char) ('0' + j); } lookUpBase64Alphabet[62] = (char) '+'; lookUpBase64Alphabet[63] = (char) '/'; } private static boolean isWhiteSpace(char octect) { return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); } private static boolean isPad(char octect) { return (octect == PAD); } private static boolean isData(char octect) { return (octect < BASELENGTH && base64Alphabet[octect] != -1); } /** * Encodes hex octects into Base64 * * @param binaryData Array containing binaryData * @return Encoded Base64 array */ public static String encode(byte[] binaryData) { if (binaryData == null) { return null; } int lengthDataBits = binaryData.length * EIGHTBIT; if (lengthDataBits == 0) { return ""; } int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets; char encodedData[] = null; encodedData = new char[numberQuartet * 4]; byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; int encodedIndex = 0; int dataIndex = 0; for (int i = 0; i < numberTriplets; i++) { b1 = binaryData[dataIndex++]; b2 = binaryData[dataIndex++]; b3 = binaryData[dataIndex++]; l = (byte) (b2 & 0x0f); k = (byte) (b1 & 0x03); byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3]; encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f]; } // form integral number of 6-bit groups if (fewerThan24bits == EIGHTBIT) { b1 = binaryData[dataIndex]; k = (byte) (b1 & 0x03); byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4]; encodedData[encodedIndex++] = PAD; encodedData[encodedIndex++] = PAD; } else if (fewerThan24bits == SIXTEENBIT) { b1 = binaryData[dataIndex]; b2 = binaryData[dataIndex + 1]; l = (byte) (b2 & 0x0f); k = (byte) (b1 & 0x03); byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2]; encodedData[encodedIndex++] = PAD; } return new String(encodedData); } /** * Decodes Base64 data into octects * * @param encoded string containing Base64 data * @return Array containind decoded data. */ public static byte[] decode(String encoded) { if (encoded == null) { return null; } char[] base64Data = encoded.toCharArray(); // remove white spaces int len = removeWhiteSpace(base64Data); if (len % FOURBYTE != 0) { return null;// should be divisible by four } int numberQuadruple = (len / FOURBYTE); if (numberQuadruple == 0) { return new byte[0]; } byte decodedData[] = null; byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; char d1 = 0, d2 = 0, d3 = 0, d4 = 0; int i = 0; int encodedIndex = 0; int dataIndex = 0; decodedData = new byte[(numberQuadruple) * 3]; for (; i < numberQuadruple - 1; i++) { if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) { return null; } // if found "no data" just return null b1 = base64Alphabet[d1]; b2 = base64Alphabet[d2]; b3 = base64Alphabet[d3]; b4 = base64Alphabet[d4]; decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); } if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) { return null;// if found "no data" just return null } b1 = base64Alphabet[d1]; b2 = base64Alphabet[d2]; d3 = base64Data[dataIndex++]; d4 = base64Data[dataIndex++]; if (!isData((d3)) || !isData((d4))) {// Check if they are PAD characters if (isPad(d3) && isPad(d4)) { if ((b2 & 0xf) != 0)// last 4 bits should be zero { return null; } byte[] tmp = new byte[i * 3 + 1]; System.arraycopy(decodedData, 0, tmp, 0, i * 3); tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); return tmp; } else if (!isPad(d3) && isPad(d4)) { b3 = base64Alphabet[d3]; if ((b3 & 0x3) != 0)// last 2 bits should be zero { return null; } byte[] tmp = new byte[i * 3 + 2]; System.arraycopy(decodedData, 0, tmp, 0, i * 3); tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); return tmp; } else { return null; } } else { // No PAD e.g 3cQl b3 = base64Alphabet[d3]; b4 = base64Alphabet[d4]; decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); } return decodedData; } /** * remove WhiteSpace from MIME containing encoded Base64 data. * * @param data the byte array of base64 data (with WS) * @return the new length */ private static int removeWhiteSpace(char[] data) { if (data == null) { return 0; } // count characters that's not whitespace int newSize = 0; int len = data.length; for (int i = 0; i < len; i++) { if (!isWhiteSpace(data[i])) { data[newSize++] = data[i]; } } return newSize; } }
md5Utils
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class MD5 { private static MessageDigest _mdInst = null; private static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; private static MessageDigest getMdInst() { if (_mdInst == null) { try { _mdInst = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { } } return _mdInst; } public static String encode(String s) { try { byte[] btInput = s.getBytes("UTF-8"); // 使用指定的字节更新摘要 getMdInst().update(btInput); // 获得密文 byte[] md = getMdInst().digest(); // 把密文转换成十六进制的字符串形式 int j = md.length; char str[] = new char[j * 2]; int k = 0; for (int i = 0; i < j; i++) { byte byte0 = md[i]; str[k++] = hexDigits[byte0 >>> 4 & 0xf]; str[k++] = hexDigits[byte0 & 0xf]; } return new String(str); } catch (Exception e) { } return null; } }
签名utils
import com.google.gson.Gson; import com.google.gson.GsonBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 签名 */ public class SignUtils { private static final Logger log = LoggerFactory.getLogger(SignUtils.class); private static Gson gson = new GsonBuilder().create(); /** * 增加认证机制的方法 */ public static String toSign(Object obj, String devId , String devKey) { String content = ""; if (obj instanceof String) { content = (String) obj; } else { if (obj != null) { // content = jsonObject.toJSONString(obj); content = gson.toJson(obj); } } String toSign = content + "&" + devId + "&" + devKey; // 注意 md5 返回小写的32位16进制字符串 String md5Str = MD5.encode(toSign); String base64Str = Base64.encode((md5Str.getBytes())); return base64Str; } public static boolean isValidRequest(String sign, Object body, String devId, String devKey, Long pushTime, String requestMethod) { if ("POST".equals(requestMethod)) { return isValidPostRequest(sign, body, devId, devKey, pushTime); } if ("GET".equals(requestMethod)) { return isValidGetRequest(sign, (String) body, devId, devKey, pushTime); } // todo 其他请求:PUT DELETE // return isValidPostRequest(sign, body, devId, devKey, pushTime); return true; } /** * POST 请求校验 * * @param sign 签名 * @param body body * @param devId * @param devKey * @param pushTime * @return */ public static boolean isValidPostRequest(String sign, Object body, String devId, String devKey, Long pushTime) { return isValidSign(sign, body, devId, devKey, pushTime); } /** * POST 请求校验 * * @param sign 签名 * @param url url * @param devId * @param devKey * @param pushTime * @return */ public static boolean isValidGetRequest(String sign, String url, String devId, String devKey, Long pushTime) { //fixme url是否需要解码 String content = ""; try { url = URLDecoder.decode(url, "UTF-8"); content = getUrlParams(url); } catch (Exception e) { e.printStackTrace(); log.info("签名认证失败:sign={},url={}", sign, url); } return isValidSign(sign, content, devId, devKey, pushTime); } /** * 校验 * * @param sign 签名 * @param content 内容 * @param devId 开发者id * @param devKey 开发者key * @param pushTime 调用时间戳 秒级别。 System.currentTimeMillis()/1000 * @return */ private static boolean isValidSign(String sign, Object content, String devId, String devKey, Long pushTime) { long l = (System.currentTimeMillis() / 1000 - pushTime); //校验时间有效性 if (l >= 300) { throw new RuntimeException("请求时间超过规定范围时间"); } //1.鉴权 String sysSign = toSign(content, devId, devKey); return sysSign.equals(sign); } /** * GET请求 * 对url的参数进行加密 * 例如:url = xxx?a=1&b=2 =>拿a=1&b=2&出来 * * @param url * @param devId * @param devKey * @return */ public static String toSignUrl(String url, String devId, String devKey) { String content = ""; try { content = getUrlParams(url); } catch (Exception e) { } return toSign(content, devId, devKey); } /** * 将url参数转换成map * * @param url www.baidu.com?aa=11&bb=22&cc=33 * @return */ private static String getUrlParams(String url) throws Exception { StringBuffer queryString = new StringBuffer(); url = url.replace("?", ";"); if (!url.contains(";")) { return url; } if (url.split(";").length > 0) { String[] arr = url.split(";")[1].split("&"); for (int i = 0; i < arr.length; i++) { String[] values = arr[i].split("="); String key = values[0]; String value = values[1]; queryString.append(key + "="); queryString.append(URLEncoder.encode(value, "UTF-8")); if (i != (arr.length - 1)) { queryString.append("&"); } } } return queryString.toString(); } /** * 推送时间 秒级别 * * @return */ public static Integer getPushTime() { Long now = (System.currentTimeMillis() / 1000); return now.intValue(); } public static Integer getTime(Long time) { Long now = (time / 1000); return now.intValue(); } public static void main(String[] args) throws Exception { Map jsonObject = new HashMap(); jsonObject.put("name", "测试"); jsonObject.put("phone", "444111"); jsonObject.put("isAreaAdmin", "测试"); List<String> list = new ArrayList<String>(); list.add("123"); list.add("123"); list.add("123"); list.add("123"); String sign = SignUtils.toSign(new Gson().toJson(list) , "appid" , "123456"); System.out.println(sign); String body = new Gson().toJson(new String[]{"123", "123", "123"}); String sign2 = SignUtils.toSign(body , "appid" , "123456"); System.out.println(body); System.out.println(sign2); String bodyMap = new Gson().toJson(jsonObject); String signbodyMap = SignUtils.toSign(bodyMap , "appid" , "123456"); System.out.println(bodyMap); System.out.println(signbodyMap); } public static Map<String, String> getGetHeader(String url, String appId, String appKey) { String signUrl = SignUtils.toSignUrl(url, appId, appKey); return getHeader(signUrl, appId); } /** * @param content body 请求体 * @return */ public static Map<String, String> getPostHeader(Object content, String appId, String appKey) { String sign = SignUtils.toSign(content, appId, appKey); return getHeader(sign, appId); } private static Map<String, String> getHeader(String sign, String appId) { Map<String, String> header = new HashMap<String, String>(); header.put("timestamp", System.currentTimeMillis() / 1000 + ""); header.put("devId", appId); header.put("sign", sign); return header; } }
http工具
import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.net.URL; import java.net.URLConnection; import java.util.List; import java.util.Map; public class HttpUtil { private static final Logger LOGGER = LoggerFactory.getLogger(HttpUtil.class); /** * 向指定URL发送GET方法的请求 * * @param url 发送请求的URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return URL 所代表远程资源的响应结果 */ public static String sendGet(String url, String param) { return sendGet(url , param, null); } public static String sendGet(String url, String param, Map<String, String> header) { String result = ""; BufferedReader in = null; try { String urlNameString = StringUtils.isBlank(param) ? url : (url + "?" + param); URL realUrl = new URL(urlNameString); // 打开和URL之间的连接 URLConnection connection = realUrl.openConnection(); // 设置通用的请求属性 connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); if (header != null) { for (Map.Entry<String, String> bean : header.entrySet()) { connection.setRequestProperty(bean.getKey(), bean.getValue()); } } // 建立实际的连接 connection.connect(); // 获取所有响应头字段 Map<String, List<String>> map = connection.getHeaderFields(); // 遍历所有的响应头字段 for (String key : map.keySet()) { System.out.println(key + "--->" + map.get(key)); } // 定义 BufferedReader输入流来读取URL的响应 in = new BufferedReader(new InputStreamReader( connection.getInputStream())); String line; while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } // 使用finally块来关闭输入流 finally { try { if (in != null) { in.close(); } } catch (Exception e2) { e2.printStackTrace(); } } return result; } public static String sendPostWithBody(String url, String jsonObject, Map<String, String> header) { String result2 = cn.hutool.http.HttpRequest.post(url) .headerMap(header, false)//头信息,多个头信息多次调用此方法即可 // .form(paramMap)//表单内容.json格式字符串 form 表单 .body(jsonObject) // body .timeout(20000)//超时,毫秒 .execute().body(); return result2; } /** * 发送post请求 * * @param url * @param params 传参格式如下 * p1=value&p2=value&.... * @return * @header 头部 */ public static String sendPost(String url, String params, Map<String, String> header) { PrintWriter out = null; BufferedReader in = null; String result = ""; try { //设置代理 //配置代理类 // Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8888)); URL urlRead = new URL(url); // URLConnection conn = urlRead.openConnection(proxy); URLConnection conn = urlRead.openConnection(); // 设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); if (header != null) { for (Map.Entry<String, String> bean : header.entrySet()) { conn.setRequestProperty(bean.getKey(), bean.getValue()); } } // 发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); // 获取输出流 out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream(), "utf-8")); // 发送请求 out.print(params); // flush缓存 out.flush(); // 获取输入流 in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); // 获取返回结果 String line; while ((line = in.readLine()) != null) { result += line; } String cookie = conn.getHeaderField("set-cookie"); } catch (Exception e) { LOGGER.error("发送 POST 请求出现异常!" + e); e.printStackTrace(); } finally { // 关闭资源 try { if (out != null) { out.close(); } if (in != null) { in.close(); } } catch (IOException ex) { ex.printStackTrace(); } } return result; } }
HttpHelper 工具类
import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.ServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; /** * 通用http工具封装 */ public class HttpHelper { private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); public static String getBodyString(ServletRequest request) { StringBuilder sb = new StringBuilder(); BufferedReader reader = null; try (InputStream inputStream = request.getInputStream()) { reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { LOGGER.warn("getBodyString出现问题!"); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { LOGGER.error(ExceptionUtils.getMessage(e)); } } } return sb.toString(); } }
拦截器验证签名
过滤器
import org.apache.commons.lang.StringUtils; import org.springframework.http.MediaType; import javax.servlet.FilterConfig; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; /** * Repeatable 过滤器 */ public class RepeatableFilter 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 && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) { requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); } if (null == requestWrapper) { chain.doFilter(request, response); } else { chain.doFilter(requestWrapper, response); } } @Override public void destroy() { } }
/** * 构建可重复读取inputStream的request * */ public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException { super(request); request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); body = HttpHelper.getBodyString(request).getBytes("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 int available() throws IOException { return body.length; } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } }
/** * Filter配置 */ @Configuration public class FilterConfig { // 匹配链接 private String urlPatterns = "/**"; @SuppressWarnings({"rawtypes", "unchecked"}) @Bean public FilterRegistrationBean someFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new RepeatableFilter()); registration.addUrlPatterns(urlPatterns); registration.setName("repeatableFilter"); registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); return registration; } }
拦截器对签名认证
import com.alibaba.fastjson.JSONObject; import com.configuration.ConfigProperties; import com.configuration.filter.RepeatedlyRequestWrapper; import com.configuration.rest.RestResult; import com.core.utils.HttpHelper; import com.core.utils.sign.SignUtils; import org.apache.commons.lang.StringUtils; 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 org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Enumeration; /** * 签名认证拦截器 */ @Component public class SignInterceptor implements HandlerInterceptor { @Autowired private ConfigProperties configProperties; private static final Logger logger = LoggerFactory.getLogger(SignInterceptor.class); @Override public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception { } @Override public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) throws Exception { } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception { String url = request.getRequestURI(); String sign = request.getHeader("sign"); String timestamp = request.getHeader("timestamp"); String devId = request.getHeader("devId"); String devValue = configProperties.getInvokeSystemMethodDevMap().get(devId); RestResult result = checkSignIsValid(sign, timestamp, devId); if (result != null) { writer(response, result); logger.info("签名认证失败:" + JSONObject.toJSONString(result)); return false; } String body = null; if (request instanceof RepeatedlyRequestWrapper) { RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; body = HttpHelper.getBodyString(repeatedlyRequest); } // body参数为空,获取Parameter的数据 if (StringUtils.isEmpty(body)) { body = getUrlPram(request); } try { boolean validPostRequest = SignUtils.isValidRequest(sign, body, devId, devValue, Long.valueOf(timestamp), request.getMethod()); if (!validPostRequest) { writer(response, new RestResult(206, "签名无效")); logger.info("{} 认证失败 参数:sign = {} , url = {} ,请求参数 = {} ,传来的devId = {}, 配置的devValue = {} , timestamp ={}", request.getMethod(), sign, url, body, devId, devValue, timestamp); return false; } } catch (Exception e) { e.getMessage(); writer(response, new RestResult(500, "系统异常")); return false; } return true; } private void writer(HttpServletResponse response, RestResult restResult) { try { response.setContentType("application/json; charset=utf-8"); response.setHeader("Cache-Control","no-cache"); response.setCharacterEncoding("utf-8"); response.getWriter().write(JSONObject.toJSONString(restResult)); response.getWriter().flush(); } catch (Exception e) { e.printStackTrace(); } // finally { // response.getWriter().close(); // } } private String getUrlPram(HttpServletRequest request) { //form 表单请求的参数 xx.x?dd=1&b=2 Enumeration<?> pNames = request.getParameterNames(); // InputStream is = null; // is = request.getInputStream(); // String bodyInfo = IOUtils.toString(is, "utf-8"); StringBuffer sb = new StringBuffer(); while (pNames.hasMoreElements()) { String pName = (String) pNames.nextElement(); // if ("signature".equals(pName)) continue; String pValue = (String) request.getParameter(pName); if (sb.length() == 0) { sb.append(pName + "=" + pValue); } else { sb.append("&" + pName + "=" + pValue); } } return sb.toString(); } private RestResult checkSignIsValid( String sign, String timestamp, String devId) { if (StringUtils.isBlank(sign)) { return new RestResult(201, "签名有误"); } if (StringUtils.isBlank(timestamp)) { return new RestResult(201, "时间戳不能为空"); } if (StringUtils.isBlank(devId)) { return new RestResult(201, "appId不能为空"); } String devValue = configProperties.getInvokeSystemMethodDevMap().get(devId); if (StringUtils.isBlank(devValue)) { return new RestResult(202, "appId无效"); } return null; } }
mvc配置添加拦截器
import com.configuration.interceptor.SignInterceptor; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.annotation.Resource; @Configuration @EnableWebMvc @ComponentScan public class MyMvcConfig implements WebMvcConfigurer { /** * 签名认证 */ @Resource private SignInterceptor signInterceptor; /** * 自定义拦截规则 */ @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(signInterceptor).addPathPatterns("/*"); } }
测试类controller
import com.alibaba.fastjson.JSONObject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import java.io.Serializable; @Controller public class TestController { @RequestMapping(value = "/test", method = RequestMethod.POST) @ResponseBody public void changeNode(@RequestBody Student data) { System.out.println(JSONObject.toJSON(data)); } } class Student implements Serializable { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
测试类运行main函数即可
import com.daan.wl.configuration.ConfigProperties; import com.daan.wl.util.HttpUtil; import com.daan.wl.util.SignUtils; import com.google.gson.Gson; import java.io.Serializable; import java.util.Map; public class Test { public static void main(String[] args) { Student student = new Student(); student.setName("测试"); student.setAge(12); String s = new Gson().toJson(student); Map postHeader = SignUtils.getPostHeader(s ); String result2 = HttpUtil.sendPostWithBody("http://127.0.0.1:7667/test", s, postHeader); System.out.println(result2); } } class Student implements Serializable { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
到此这篇关于springboot 实现Http接口加签、验签操作方法的文章就介绍到这了,更多相关springboot Http接口加签、验签内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!