这两天一直在写Open Api,Api是写好了,但是为了安全,所以必须需要进行对接口进行认证。
public static String encryptBASE64(byte[] key) throws Exception {
return (new BASE64Encoder()).encodeBuffer(key);
}
public static byte[] decryptBASE64(String key) throws Exception {
return (new BASE64Decoder()).decodeBuffer(key);
}
MessageDigest类为应用程序提供信息摘要算法的功能,如MD5或SHA算法,信息摘要是安全的单向的哈希函数,它接受任意大小的数据,并输出固定长度的哈希值。
(a)对象开始初始化
(b)调用ipdate方法处理数据
(c) 调用digest方法完成哈希计算
public class MessageDigestTest {
static String abc = "zhaoxiaodong";
public static void main(String[] args) throws Exception{
//MessageDigest 对象开始被初始化
MessageDigest md5 = MessageDigest.getInstance("MD5");
//通过调用update方法处理数据
md5.update(abc.getBytes());
//调用digest方法完成哈希计算
System.out.println("md5(abc)="+ Base64Coder.encode(md5.digest()));
}
}
考虑到消息摘要的不安全性-拿到原文并知道算法,就可以得到正确的消息摘要。人们又加入了密钥的元素,得到了安全消息摘要——MAC(Message Authentication Code)。MAC的特点是仅通过原文是无法计算出安全消息摘要的。还需要一个双方都知道的消息密钥。如果有人修改了消息本体,有修改了消息摘要,但是因为没有密钥,那么将被发现破坏了数据,计算MAC的方法有很多,但是核心Java API没有相关实现。Java的实现主要是通过JCE提供者来实现的。Mac类对应消息摘要的MessageDigest类。计算消息摘要时需要一个密钥。密钥的管理是另一个话题了。这里主要讲MAC的算法,JCE实现主要基于HmacSHA1和HmacMD5。具体provider提供了哪些MAC相关的算法,它们比较如何,见下面的代码:
(a)getInstance得到实例
(b)传入key和算法参数进行初始化
(c)通过update进行传入数据
(d)通过doFinal得到结果
public class Md5Test {
static String appId = "zhaoxiaodong";
static String appKey ="nizhenbang";
public static String main(String[] args) throws Exception{
//1、getInstance得到实例
Mac mac = Mac.getInstance("HmacSHA1");
//2、传入key和算法参数进行初始化
mac.init(new SecretKeySpec(appId.getBytes("utf-8"),"HmacSHA1"));
//3、通过update进行传入数据
//4、通过doFinal得到结果
byte[] signData = mac.doFinal(appKey.getBytes("utf-8"));
return Base64.encodeBase64String(signData);
}
}
package com.openApi;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.Cipher;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
/**
* @Describe: 首先我们要考虑是不是可逆的还是不可逆的。
* @Author: 赵晓东
* @Date: 17:03 2021-1-22
* @param:
* @return:
*/
public class openAPi {
//首先声明一个静态的常量appId
static String appId = "10037e6f66f2d0f901672aa27d690006";
static String url ="http://192.168.72.15:61043/app/orderApi/orderClose";
public static void main(String[] args) throws Exception {
//接下来就是对appId进行加密操作,我们就采用不可逆的吧
// byte[] appIdByte = appId.getBytes("UTF-8");
// byte[] bytes = DigestUtils.md5(appIdByte);
//实例化TenantEntity
TenantEntity tenantEntity = new TenantEntity();
tenantEntity.setAccount("15197272363");
tenantEntity.setCode("1113");
String tenantJson = JSONObject.toJSONString(tenantEntity);
//然后将这些发送到getOpenBodySig
String send = send(url, tenantJson);
System.out.println(send);
// String openBodySig = getOpenBodySig(appId, tenantJson);
// System.out.println(openBodySig);
// 接下来获取到Token之后,就是对发送数据给url
// request()
}
public static String getOpenBodySig(String appId,String entity) throws Exception{
//MessageDigest对象开始被初始化
MessageDigest md5 = MessageDigest.getInstance("MD5");
//通过调用update方法处理数据
String encodeBody= appId+entity;
md5.update(encodeBody.getBytes("utf-8"));
//调用digest方法完成哈希计算
String appIdEncode = Base64.encodeBase64String(md5.digest());
//appId已进行加密
// byte[] bytes = DigestUtils.md5(appId);
//将appId和entity一起进行加密
//接下来是需要把商家的信息也封装起来,然后传入到token中,到过滤器进行解析
System.out.println("------------->"+ appIdEncode);
// String token = JSONObject.toJSONString(appIdEncode);
return appIdEncode;
}
public static String send(String url,String entity) throws Exception{
String openBodySig = getOpenBodySig(appId, entity);
String requestString = request(url, openBodySig, entity);
return requestString;
}
public static String request(String url,String authorization,String entity)throws Exception{
String response="";
PrintWriter out = null;
BufferedReader in = null;
try {
URL realUrl = new URL(url);
URLConnection conn = realUrl.openConnection();
HttpURLConnection httpURLConnection=(HttpURLConnection)conn;
httpURLConnection.setRequestProperty("Content-Type","application/json");
httpURLConnection.setRequestProperty("authorization",authorization);
// String encodeEntity = (new BASE64Encoder()).encodeBuffer(entity.getBytes());
httpURLConnection.setRequestProperty("entity",entity);
httpURLConnection.setDoOutput(true);
httpURLConnection.setDoInput(true);
out = new PrintWriter(httpURLConnection.getOutputStream());
out.write(entity);
out.flush();
httpURLConnection.connect();
in = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream()));
String line;
while ((line = in.readLine())!=null){
response +=line;
}
} catch (Exception e){
e.printStackTrace();
}finally {
try {
if ( out!=null){
out.close();
}
if (in!=null){
in.close();
}
}catch (Exception ex){
ex.printStackTrace();
}
}
return response;
}
}
校验的时候直接在拦截器进行拦截,然后进行校验。
//如果是从Swagger中,则直接走下面--earnest
String entity = request.getHeader("entity");
//判断这个url是否是外部的地址,如果是外部的地址就走这个,如果不是就走下面
if(urlMatchUtil.match("/app/orderApi",requestURI) && entity!=null && entity!=""){
//获取到传入的参数,传入的参数中有code值,通过code值,就可以查询appId,然后就可以进行判断了。
//对entity进行解码
// byte[] bytes1 = (new BASE64Decoder()).decodeBuffer(entity);
// String entityInfo = new String(bytes1);
TenantEntity tenantEntity = JSON.parseObject(entity, TenantEntity.class);
//获取到code值
String code = tenantEntity.getCode();
//通过code值查询数据库获取到appId
String appId = "10037e6f66f2d0f901672aa27d690006";
//获得了appId和entity,然后对他们进行加密判断传进来的时候合法
String appIdEntity = appId + entity;
//MessageDigest对象开始被初始化
MessageDigest md5 = MessageDigest.getInstance("MD5");
//通过调用update方法处理数据
md5.update(appIdEntity.getBytes("utf-8"));
//调用digest方法完成哈希计算
String appIdEncode = Base64.encodeBase64String(md5.digest());
String authorization = request.getHeader("authorization");
appIdEncode = new String(appIdEncode.getBytes("ISO-8859-1"),"UTF-8");
authorization = new String(authorization.getBytes("ISO-8859-1"),"UTF-8");
System.out.println(appIdEncode);
System.out.println(authorization);
if (appIdEncode.trim().equals(authorization.trim())){
return super.preHandle(request,response,handler);
}else {
throw BizException.wrap(R.FAIL_CODE,"参数不合法");
}
}
这次优化主要优化了将请求实体进行加密、加入了统一返回,不用再分别添加请求头,再一个就是增加了时间戳,api中加入时间戳的目的,因为这些api是异步请求的,为了防止重复请求,按照调用顺序被处理,需要精确获取客户端时间等原因,需要时间戳,在URL中加时间戳就会保证每一次发起的请求都是一个不同于之前的请求,这样就能避免浏览器对URL的缓存。
(1)请求方
public static String getOpenBodySig(String appId,String entity ) throws Exception{
String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
//MessageDigest对象开始被初始化
MessageDigest md5 = MessageDigest.getInstance("MD5");
//通过调用update方法处理数据
String encodeBody= appId+entity;
md5.update(encodeBody.getBytes("utf-8"));
//调用digest方法完成哈希计算
String appIdEncode = Base64.encodeBase64String(md5.digest());
//appId已进行加密
// byte[] bytes = DigestUtils.md5(appId);
//将appId和entity一起进行加密
//接下来是需要把商家的信息也封装起来,然后传入到token中,到过滤器进行解析
System.out.println("------------->"+ appIdEncode);
// String token = JSONObject.toJSONString(appIdEncode);
//对entity进行加密
String entityBase = Base64.encodeBase64String(entity.getBytes("UTF-8"));
return "OPEN-BODY-SIG appIdEncode="+"\""+appIdEncode+"\""+", entityBase="+"\""+entityBase+"\""+",timestamp="+"\""+timestamp+"\"";
}
//如果是从Swagger中,则直接走下面--earnest
//判断这个url是否是外部的地址,如果是外部的地址就走这个,如果不是就走下面
if (urlMatchUtil.match("/app/orderApi", requestURI)) {
//获取到传入的参数,传入的参数中有code值,通过code值,就可以查询appId,然后就可以进行判断了。
String authorization = request.getHeader("authorization");
//接下来,我们需要截取 authorization,然后获取相应的值
int i = authorization.indexOf("=");
int a = authorization.indexOf(",");
//"r1F1Jb/GowKLkqCwd6y0eg=="
String appIdEncodeInfo = authorization.substring(i + 1, a);
int appIdEncodeFirst = appIdEncodeInfo.indexOf("\"");
int appIdEncodeLast = appIdEncodeInfo.lastIndexOf("\"");
//这个是最终的截取的appId加密的值
String appIdCode = appIdEncodeInfo.substring(appIdEncodeFirst + 1, appIdEncodeLast);
String entityBase = org.apache.commons.lang3.StringUtils.substringAfter(authorization, "entityBase=");
//进行二次截取
String entityBaseInfo = org.apache.commons.lang3.StringUtils.substringBefore(entityBase, ",timestamp=");
int entityBaseFist = entityBaseInfo.indexOf("\"");
int entityBaseLast = entityBaseInfo.lastIndexOf("\"");
//这个是最终截取的entity值
String entityCode = entityBaseInfo.substring(entityBaseFist + 1, entityBaseLast);
//获取到截取的时间戳
String timestampInfo = org.apache.commons.lang3.StringUtils.substringAfterLast(authorization, "timestamp=");
int timestampFirst = timestampInfo.indexOf("\"");
int timestapLast = timestampInfo.lastIndexOf("\"");
//这个是最终截取的时间戳
String timeStampCode = timestampInfo.substring(timestampFirst + 1, timestapLast);
DateFormat df1 = new SimpleDateFormat("yyyyMMddHHmmss");
Date time1 = df1.parse(timeStampCode);
long time = time1.getTime();
//接下来要做的是对时间戳进行判断了
if (StringUtils.isEmpty(timeStampCode)) {
throw BizException.wrap(R.FAIL_CODE,"时间戳为空");
}
if (System.currentTimeMillis()-time>requestTimestampLimit){
throw BizException.wrap(R.FAIL_CODE,"请求时间已经太长了");
}
//接下来就是对entityBaseInfo进行解码
byte[] bytes = Base64.decodeBase64(entityCode);
String entityTenant = new String(bytes);
TenantEntity tenantEntity = JSON.parseObject(entityTenant, TenantEntity.class);
//获取到code值
String code = tenantEntity.getCode();
if (StringUtils.isEmpty(code)){
throw BizException.wrap(R.FAIL_CODE,"此商家编码为空");
}
//判断数据库中是否有此商家
boolean checkTenant = tenantService.check(code);
if (!checkTenant) {
throw BizException.wrap(R.FAIL_CODE,"没有此商家编码");
}
//通过code获取appId
R appId = dockmentService.getAppIdByCode(code);
if (StringUtils.isEmpty(appId)){
throw BizException.wrap(R.FAIL_CODE,"此商家的appId为空,请联系管理员");
}
//通过code值查询数据库获取到appId
// String appId = "10037e6f66f2d0f901672aa27d690006";
//获得了appId和entity,然后对他们进行加密判断传进来的时候合法
String appIdEntity = appId + entityTenant;
//MessageDigest对象开始被初始化
MessageDigest md5 = MessageDigest.getInstance("MD5");
//通过调用update方法处理数据
md5.update(appIdEntity.getBytes("utf-8"));
//调用digest方法完成哈希计算
String appIdEncode = Base64.encodeBase64String(md5.digest());
//统一编码
appIdEncode = new String(appIdEncode.getBytes("ISO-8859-1"), "UTF-8");
appIdCode = new String(appIdCode.getBytes("ISO-8859-1"), "UTF-8");
if (appIdEncode.trim().equals(appIdCode.trim())) {
return super.preHandle(request, response, handler);
} else {
throw BizException.wrap(R.FAIL_CODE, "参数不合法");
}
}
希望有小伙伴们一起讨论一下,在使用Mac、MessageDigest请求进来之后,服务方如何去做校验的,可以在下方留言。