API接口签名生成算法和签名验证算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33721382/article/details/85856093

1、参考网上资料和书本资料,实现了API接口签名生成算法和签名验证算法。

(1)参考资料:https://www.jianshu.com/p/d47da77b6419

(2)参考书籍:高级软件架构师教程(311-312)

2、API接口签名生成算法

主要步骤如下:

(1)将所有业务请求参数按字母先后顺序排序。

(2)参数名称和参数值链接成一个字符串A。

(3)在字符串A的首尾加上apiSecret接口密匙组成一个新字符串B。

(4)对字符串进行MD5散列运算得到API签名sign,然后再进行Base64编码。

假设请求的参数为:f=1,b=23,k=33,排序后为b=23,f=1,k=33,参数名和参数值链接后为b23f1k33,首尾加上appsecret后md5:

C = md5(secretkey1value1key2value2...secret);

Base64.encode(C );

以上签名方法安全有效地解决了参数被篡改和身份验证的问题,如果参数被篡改,没事,因为别人无法知道apiSecret,也就无法重新生成新的sign,从而保证接口调用是可靠的。

Java代码

/**

* 添加参数到HTTP请求里面

* @param method

* @param path

* @param params

* @param apiKey

* @param apiSecret

*/

void addRequiredParams(String method, String path, Map<String, String> params, String apiKey, String apiSecret) {

params.put("key", apiKey); //标识

params.put("sigVer", "1"); //签名版本

String ts = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss:SSS").format(LocalDateTime.now());

params.put("ts", ts); //时间戳

params.put("nonce", RandomStringUtils.randomAlphanumeric(16));

String sig = getSig(method, path, apiSecret, params);

params.put("sig", sig); //API签名

}



/**

* API签名生成

*

* @param method

* @param path

* @param apiSecret

* @param params

* @return

*/

private String getSig(String method, String path, String apiSecret, Map<String, String> params) {

StringBuilder stringA = new StringBuilder();

///1、将所有业务请求参数按字母先后顺序排序.

// 2、参数名称和参数值链接成一个字符串A

Set<String> keySet = new TreeSet<>(params.keySet());

for (String key : keySet) {

String value = params.get(key);

if (value == null) {

continue;

}

System.out.println(key + ":" + params.get(key));

stringA.append(key);

stringA.append("=");

stringA.append(params.get(key));

stringA.append("&");

}

stringA.setLength(stringA.length() - 1); // trim the last "&"



System.out.println("参数名称和参数值链接成一个字符串A:" + stringA.toString());

logger.debug("参数名称和参数值链接成一个字符串A:" + stringA.toString());



String unifiedString = method.toUpperCase() + ":" + path + ":" + stringA.toString();

logger.debug("unified string: " + unifiedString);



String sig = null;

///3、在字符串A的首尾加上apiSecret组成一个新字符串B

StringBuilder stringB = new StringBuilder();

stringB.append(apiSecret).append(stringA).append(apiSecret);

try {

//4、对字符串进行MD5散列运算得到签名sign,然后再进行Base64编码

byte[] bytes = Base64.getEncoder().encode(MD5Utils.md5(stringB.toString()).getBytes("UTF-8"));

sig = new String(bytes, "UTF-8");

} catch (Exception e) {

logger.error("getSig error: " + e.getMessage());

}

return sig;

}



3、签名验证算法(接口提供方验证接口请求是否可信,主要算法跟生成API签名的算法是一样的)

主要步骤如下:

1、得到请求方携带的API签名。

2、将所有业务请求参数按字母先后顺序排序。

3、参数名称和参数值链接成一个字符串A。

4、在字符串A的首尾加上apiSecret接口密匙组成一个新字符串B。

5、对新字符串B进行MD5散列运算生成服务器端的API签名,将客户端的API签名进行Base64解码,然后开始验证签名。

6、如果服务器端生成的API签名与客户端请求的API签名是一致的,则请求是可信的,否则就是不可信的。

java代码

/**

* 检查API签名是否合法

* (1)客户端请求里面会携带签名(客户端利用apiSecret和给定的算法产生签名)

* (2)服务器端会使用存在服务器端的apiSecret和相同的算法产生一个签名。

* (3)服务器端对这两个签名进行校验,得出签名的有效性。如果有效,则正常走业务流程,否则拒绝请求。

*

* @param request

* @param apiSecret

* @return

*/

public static boolean checkSig(HttpServletRequest request, String apiSecret) throws IOException{



//1、得到请求方携带的API签名

String clientSig = null;

StringBuilder stringA = new StringBuilder();



Enumeration<String> parameterNames = request.getParameterNames();

Set<String> keySet = new TreeSet<>();

//2、将所有业务请求参数按字母先后顺序排序。

while (parameterNames.hasMoreElements()) {

String parameterName = parameterNames.nextElement();

if (parameterName.equals("sig")) {

//获取客户端的API签名

clientSig = request.getParameter("sig");

continue;

}

keySet.add(parameterName);

}

//3、参数名称和参数值链接成一个字符串A。

for (String key : keySet) {

String value = request.getParameter(key);

if (value == null) {

continue;

}

stringA.append(key);

stringA.append("=");

stringA.append(value);

stringA.append("&");

}

stringA.setLength(stringA.length() - 1); // trim the last "&"

//服务器端根据参数生成的签名

String serverSig = null;

//Base64解码客户端的签名

clientSig = new String(Base64.getDecoder().decode(clientSig), "UTF-8");

//4、在字符串A的首尾加上apiSecret接口密匙组成一个新字符串B。

StringBuilder stringB = new StringBuilder();

stringB.append(apiSecret).append(stringA).append(apiSecret);



//5、对新字符串B进行MD5散列运算生成服务器端的API签名,将客户端的API签名进行Base64解码,然后开始验证签名。

//6、如果服务器端生成的API签名与客户端请求的API签名是一致的,则请求是可信的,否则就是不可信的。

serverSig = MD5Utils.md5(stringB.toString());

return clientSig != null && serverSig != null && clientSig.equals(serverSig);

}





3、使用过滤器统一对访问OpenApi的请求进行API签名验证

使用过滤器的好处:统一对请求进行预处理,即验证API签名。如果签名验证失败则拒绝请求,并返回API签名验证失败的消息给请求方。

过滤器代码如下:

/**

* 描述: 验证OpenAPI接口签名的过滤器

*

* @author chenlw

* @create 2018-11-19 11:21

*/

public class OpenApiRequestCheckFilter implements Filter {



@Autowired

private SystemConnectionConfigClient systemConnectionConfigClient;





@Override

public void init(FilterConfig filterConfig) throws ServletException {



}





@Override

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {

System.out.println("-----------------OpenApiRequestCheckFilter--------------------");

HttpServletRequest request = (HttpServletRequest) servletRequest;

HttpServletResponse response = (HttpServletResponse) servletResponse;



if (systemConnectionConfigClient == null) {

//解决@Autowired无法自动注入的问题

BeanFactory factory = WebApplicationContextUtils.getRequiredWebApplicationContext(request.getServletContext());

systemConnectionConfigClient = (SystemConnectionConfigClient) factory.getBean("systemConnectionConfigClient");

}



String apiKey = request.getParameter("key"); //apiKey

if(apiKey == null){

///API签名验证失败,把请求转发到处理不合法请求的Controller

UnauthorizedForward(request,response);

return;

}

String apiSecret = systemConnectionConfigClient.queryDcToFrontApiSecretByApiKey(apiKey);

//数据中心请求前置的接口密匙

if (apiSecret != null) {

///对API接口签名进行验证

boolean sigCheckResult = OpenApiRequestCheckUtils.checkSig(request, apiSecret);

if (sigCheckResult) {

//API签名验证通过

chain.doFilter(request, response);

} else {

///API签名验证失败,把请求转发到处理不合法请求的Controller

UnauthorizedForward(request,response);

}

} else {

///API签名验证失败,把请求转发到处理不合法请求的Controller

UnauthorizedForward(request,response);

}

}





private void UnauthorizedForward(HttpServletRequest request,HttpServletResponse response) throws IOException,ServletException{

///查询不到接口配置,把请求转发到处理不合法请求的Controller

if ("GET".equals(request.getMethod())) {

request.getRequestDispatcher("/api/dataCenterRequest/handleUnauthorizedGetRequest").forward(request, response);

} else {

request.getRequestDispatcher("/api/dataCenterRequest/handleUnauthorizedPostRequest").forward(request, response);

}

}



@Override

public void destroy() {



}

}



猜你喜欢

转载自blog.csdn.net/qq_33721382/article/details/85856093