编写第三方接口供别人调用时,如何保证安全性


在这里插入图片描述
在这里插入图片描述

参考链接:对称加密和非对称加密

工具类

建表

用来保存加密的秘钥等消息,第三方可通过某一条数据进行接口的调用

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("&timestamp=").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);

    }

}

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值