![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/e5fc6b858db6c07d95cf5e9eaf3202dd.jpeg)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/0a9f0dd4168037d136c73335dcbb7c18.jpeg)
参考链接:对称加密和非对称加密
工具类
建表
用来保存加密的秘钥等消息,第三方可通过某一条数据进行接口的调用
CREATE TABLE `api_config` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'api配置',
`name` varchar(60) DEFAULT NULL COMMENT '名称',
`relation` varchar(60) NOT NULL COMMENT '归属',
`app_id` varchar(32) NOT NULL COMMENT 'appId',
`app_secret` varchar(100) NOT NULL COMMENT 'appSecret',
`aes_key` varchar(100) DEFAULT NULL COMMENT 'key',
`iv` varchar(100) DEFAULT NULL COMMENT '向量',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime DEFAULT NULL COMMENT '修改时间',
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
`memo` varchar(60) DEFAULT NULL COMMENT '名称',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `iv` (`iv`) USING BTREE,
UNIQUE KEY `aes_key` (`aes_key`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
其中,name和relation用来保存调用方的相关信息;app_id,app_secret,aes_key,iv可通过AES秘钥生成器生成,生成的数据不能带各种符号
实体类
@Table(name = "api_config")
public class ApiConfig implements Serializable {
/**api配置*/
@Id
@Column(name = "id")
private String id;
/**名称*/
@Column(name = "name")
private String name;
/**归属*/
@Column(name = "relation")
private String relation;
/**appId*/
@Column(name = "app_id")
private String appId;
/**appSecret*/
@Column(name = "app_secret")
private String appSecret;
/**向量*/
@Column(name = "iv")
private String iv;
/**更新时间*/
@Column(name = "update_time")
private java.util.Date updatedTime;
/**创建时间*/
@Column(name = "created_at")
private java.util.Date createdAt;
/**修改时间*/
@Column(name = "updated_at")
private java.util.Date updatedAt;
/**删除时间*/
@Column(name = "deleted_at")
private java.util.Date deletedAt;
/**备注*/
@Column(name = "memo")
private String memo;
/**key*/
@Column(name = "aes_key")
private String aes_key;
}
调用的工具
public class SignUtils {
@Resource
private ApiCfgService apiCfgService;
// 校验 appid,并进行解密获取数据
public String getData(SignQueryDTO bean) {
log.trace("auth type:{}", JSONObject.toJSONString(bean));
long timestamp = Long.parseLong(bean.getTimestamp());
if (((System.currentTimeMillis() - timestamp) > 24*60*60*1000)) {//超过24小时
throw new ScException("登录链接过期");
}
//通过appid获取加解密配置数据
ApiConfigDTO apiConfig = getApiConfig(bean.getAppid());
//通过appid,secret(由appid查出的加解密配置秘钥),timestamp,nonce,content 验证 signature(由调用方通过appid,secret,content, nonce, timestamp生成)
boolean isSuccess = checkSignature(bean.getSignature(), bean.getAppid(), bean.getContent(), bean.getNonce(), bean.getTimestamp(), apiConfig.getAppSecret());
if (!isSuccess) {
throw new ScException(RespCode.INVALID_SIGNATURE);
}
//根据加密类型对数据进行解密
String ct = null;
if (ApiAuth.NONE.equals(bean.getSignValue())) {
byte[] encData = Base64.getDecoder().decode(AES.uglify(bean.getContent()));
ct = new String(encData);
}
if (ApiAuth.AES.equals(bean.getSignValue())) {
ct = AES.decrypt(apiConfig.getIv(), apiConfig.getAes_key(), bean.getContent());
}
return ct;
}
/**
* 获取api config 配置
* @param appid
* @return
*/
private ApiConfigDTO getApiConfig(String appid) {
ApiConfigDTO apiConfigDTO = new ApiConfigDTO();
apiConfigDTO.setAppId(appid);
Result<ApiConfigDTO> rt = apiCfgService.getApiConfig(ScidContext.getScid(), apiConfigDTO);
if (rt == null || rt.getData() == null) {
throw new ScException(RespCode.INVALID_APPID);
}
return rt.getData();
}
}
apiCfgService.getApiConfig
/**
* 通过ApiConfig的id获得ApiConfig对象
*
* @return
*/
@Override
public Result show(ApiConfig bean) {
if (StringUtils.hasText(bean.getAppId())) {
Optional<ApiConfig> api = apiConfigRepository.findFirstByAppIdAndDeletedAtIsNull(bean.getAppId());
return success(api.get());
}
Optional<ApiConfig> option = apiConfigRepository.findById(bean.getId());
if (!option.isPresent()) {
return success();
}
ApiConfig entity = option.get();
Map<String, MyLink> linkMap = LinkUtil.convertLink(bean.getClass());
List<ApiConfig> entitys = new ArrayList<>();
entitys.add(entity);
PageImpl page = new PageImpl(entitys);
return success(entity);
}
校验签名:通过appid,content,nonce,timestamp和secret生成签名,再与传入的签名进行对比
public static boolean checkSignature(String signature, String appid, String content, String nonce, String timestamp, String secret) {
String resultStr = null;
try {
//根据appid, secret, content, nonce, timestamp生成签名
resultStr = SignUtil.sign(appid, content, nonce, timestamp, secret);
} catch (Exception var8) {
var8.printStackTrace();
}
return resultStr != null && resultStr.equals(signature);
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiAuth {
String AES = "AES";
String NONE = "NONE";
String value() default "AES";
}
第三方提供方
通过获取的签名和加密的传输内容调用接口
/**
* 添加数据的外部接口
*
* @param signQueryDTO 条件
* @return {@link Result<Boolean>}
*/
@PostMapping(value = {"/sync/add"}, produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
public Result<Boolean> add(@RequestBody @Validated SignQueryDTO signQueryDTO) {
//调用工具类进行校验权限 获取数据
String data = signUtils.getData(signQueryDTO);
GmBasicPointCreateDTO createDTO = JSON.parseObject(data, GmBasicPointCreateDTO.class);
//校验数据格式
SyncCheckParams(createDTO);
return success(Objects.nonNull(iGmBasicPointService.create(createDTO)));
}
public class SignQueryDTO {
@NotBlank(message = "appid 不能为空")
private String appid;
@NotBlank(message = "content 不能为空")
private String content;
@NotBlank(message = "nonce 不能为空")
private String nonce;
@NotBlank(message = "timestamp 不能为空")
private String timestamp;
@NotBlank(message = "signature 不能为空")
private String signature;
/**
* AES default
* NONE can choose
*/
private String signValue = "AES";
}
第三方调用端
传输数据的实体类
/*样例*/
@Data
@AllArgsConstructor
@NoArgsConstructor
class Store {
private String address;
private String charge;
private String originId;
private Date createdAt;
private String name;
private String phone;
private Date updatedAt;
private String regionPid;
private String regionId;
private String regionCid;
}
AES工具类
这里使用AES/CBC/PKCS7Padding的数据填充方式
/**
* AES/CBC/PKCS5Padding 对称加密
* @author Born
*/
public class AES {
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
/**
* 数据加密
*
* @param iv
* @param srcData
* @return
*/
public static String encrypt(String iv, String key, String srcData) {
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher;
String encoded = null;
try {
cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes()));
byte[] bytes = cipher.doFinal(srcData.getBytes());
encoded = Base64.getEncoder().encodeToString(bytes);
encoded = pretty(encoded);
} catch (Exception e) {
e.printStackTrace();
}
return encoded;
}
/**
* 数据解密
*
* @param
* @param
* @param
* @return
*/
public static String decrypt(String iv, String key, String encoded) {
encoded = uglify(encoded);
byte[] encData = Base64.getDecoder().decode(encoded);
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher;
String srcData = null;
try {
cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv.getBytes()));
byte[] bytes = cipher.doFinal(encData);
srcData = new String(bytes);
} catch (Exception e) {
e.printStackTrace();
}
return srcData;
}
public static String pretty(String encoded) {
char[] prettyChars = {'-','_'};
char[] uglyChars = {'+', '/'};
for(int i=0;i<prettyChars.length;i++) {
char patten = uglyChars[i];
char replace = prettyChars[i];
encoded = encoded.replace(patten, replace);
}
return encoded;
}
public static String uglify(String encoded) {
char[] prettyChars = {'-','_'};
char[] uglyChars = {'+', '/'};
for(int i=0;i<uglyChars.length;i++) {
char patten = prettyChars[i];
char replace = uglyChars[i];
encoded = encoded.replace(patten, replace);
}
return encoded;
}
}
MD5加密工具类
/**
* MD5 加密
* @author Born
*/
@Slf4j
public class MD5Util {
/**
* md5加密
*
* @param text
* @return
*/
public static String md5(String text) {
return digest(text, "md5");
}
/**
* 返回十六进制字符串
*
* @param arr
* @return
*/
private static String hex(byte[] arr) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < arr.length; ++i) {
sb.append(Integer.toHexString((arr[i] & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString();
}
/**
* md5或者sha-1加密
*
* @param text 要加密的内容
* @param algorithm 加密算法名称:md5或者sha-1,不区分大小写
* @return
*/
private static String digest(String text, String algorithm) {
if (text == null) {
throw new IllegalArgumentException("请输入要加密的内容");
}
algorithm = algorithm.toLowerCase();
if (algorithm == null || "".equals(algorithm.trim())) {
algorithm = "md5";
}
if (!"md5".equals(algorithm) && !"sha-1".equals(algorithm)) {
throw new IllegalArgumentException("invalid algorithm for digest");
}
try {
MessageDigest inst = MessageDigest.getInstance(algorithm);
inst.update(text.getBytes("UTF8"));
byte bytes[] = inst.digest();
return hex(bytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
public static String signature(String appid, String content, String nonce, String timestamp, String secret) {
try {
//sorted by key
TreeMap<String, String> map = new TreeMap<String, String>();
map.put("appid", appid);
map.put("content", content);
map.put("nonce", nonce);
map.put("timestamp", timestamp);
return signature(map, secret);
} catch (Exception e) {
log.info(e.getMessage());
}
return null;
}
public static String signature(Map<String, String> kv, String secret) {
try {
//sorted by key
TreeMap<String, String> map = new TreeMap<String, String>();
for (Entry<String, String> entry : kv.entrySet()) {
map.put(entry.getKey(), entry.getValue());
}
//URLEncoder.encode
StringBuffer buffer = new StringBuffer();
for (Entry<String, String> entry : map.entrySet()) {
try {
buffer.append(entry.getKey()).append("=")
.append(URLEncoder.encode(entry.getValue(), "UTF-8")).append("&");
} catch (UnsupportedEncodingException e) {
log.info("URLEncoder.encode UnsupportedEncodingException");
throw e;
}
}
buffer.append("secret").append("=").append(URLEncoder.encode(secret, "UTF-8"));
log.info(buffer.toString());
//MD5Util.md5
String signature = MD5Util.md5(buffer.toString());
return signature;
} catch (Exception e) {
log.info("signature: " + e.getMessage());
}
return null;
}
public static String signature(MultiValueMap<String, String> form, String secret) {
TreeMap<String, String> map = new TreeMap<String, String>();
for (String entry : form.keySet()) {
map.put(entry, form.getFirst(entry));
}
return signature(map, secret);
}
}
调用方获取签名和加密的传输内容
/**
* 接口加密封装
* @author Born
*/
public class SignUtil {
/**
* appid:value、content:value、nonce:value、timestamp:value,secret:value五个键值对拼接成字符串,然后MD5加密,得到cipherText的值
* 调用端通过appid,secret,content(),timestamp(时间戳),nonce(随机字符串)进行加密生成签名
* @return
*/
public static String sign(String appid, String content, String nonce, String timestamp, String secret) {
StringBuffer buffer = new StringBuffer();
buffer
.append("appid=").append(appid)
.append("&content=").append(content)
.append("&nonce=").append(nonce)
.append("×tamp=").append(timestamp)
.append("&secret=").append(secret);
String jsonString = buffer.toString();
return MD5Util.md5(jsonString);
}
public static void main(String[] args) {
// JSONObject object = JSONObject.parseObject("{\"uniqueCode\":\"xxxxxx\",\"startDate\":\"2022-05-18\",\"endDate\":\"2022-05-18\"}");
Store store = new Store("广东省广州市从化区", "silence", "12345678912347852", new Date(),
"GB12500681", new BigDecimal("22.78340238797784"), new BigDecimal("113.8787848242057740"), "麦当劳(公明大街)", "13668888888", 99, new Date(),
"3", "83278908781686", "832789087816818");
JSONObject object = (JSONObject) JSONObject.toJSON(store);
//根据数据库配置的aes_key和iv 对传输内容进行加密,将加密后的密文作为入参调用接口
String content = AES.encrypt("gdgdgdghhjb", "tgffgggg544", object.toString());
System.out.println("content===" + content);
//数据库配置的app_id,app_secret
String appid = "fshfhkkgdghbjkbng";
String secret = "yfhjmgjfg34534";
//自己配置的随机字符串
String nonce = "48987dds";
String timestamp = "1668677539000";
//获取签名
String signature = sign(appid, content, nonce, timestamp, secret);
System.out.println("signature===" + signature);
}
}