平台接入实现方案

引言

互联网上的大平台都会对外提供api,但这些api不能不通过任何验证就能直接访问,这样风险会非常高,也是不合理的,比如微信公众号,七牛云,阿里巴巴相关应用的接入等等,我们接触最多的客户端的实现,平台端很少有人知道是怎么做到的,下面我们一起学习了解一下。

平台端

  1. 定义生成appId和appSecret的工具类

AppUtils.java

package com.xxx.common.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.UUID;

/**
 *
 @Title: AppUtils
 @Description: 随机产生唯一的app_id和app_secret
 @date 2024/01/16 17:07
 */
public class AppUtils {
    //生成 app_secret 密钥
    private final static String SERVER_NAME = "XXXSYWXT09";
    private final static String[] chars = new String[]{"a", "b", "c", "d", "e", "f",
            "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
            "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
            "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
            "W", "X", "Y", "Z"};
    private final static String[] upperChars = new String[]{"0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
            "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
            "W", "X", "Y", "Z"};
    public static final String ALLCHAR = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    public static final String UPPERCHAR = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    /**
     * <p>
     *  获取appId
     *  </P>
     *  @date 2024/01/16 17:10
     */
    public static String getAppId() {
        return SERVER_NAME + getUpperRandomString(6);
    }

    /**
     * <p>
     *  生成appSecret
     * </P>
     * @date 2024/01/16 17:30
     */
    public static String getAppSecret(String appId) {
        try {
            return getRandomString(16);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }

    /**
     * 返回一个定长的随机字符串(只包含大写字母、数字)
     *
     * @param length
     *            随机字符串长度
     * @return 随机字符串
     */
    public static String getUpperRandomString(int length) {
        StringBuffer sb = new StringBuffer();
        SecureRandom random = new SecureRandom();
        for (int i = 0; i < length; i++) {
            sb.append(UPPERCHAR.charAt(random.nextInt(UPPERCHAR.length())));
        }
        return sb.toString();
    }

    /**
     * 返回一个定长的随机字符串(只包含大小写字母、数字)
     *
     * @param length 随机字符串长度
     * @return 随机字符串
     */
    public static String getRandomString(int length) {
        StringBuffer sb = new StringBuffer();
        SecureRandom random = new SecureRandom();
        for (int i = 0; i < length; i++) {
            sb.append(ALLCHAR.charAt(random.nextInt(ALLCHAR.length())));
        }
        return sb.toString();
    }
}
  1. 服务启动时加载appId和appSecret到缓存
	/**
	 * 服务启动时加载appId和appSecret到缓存
	 */
    @Override
    @PostConstruct
    public void initThirdAppSecret()
    {
        App app = new App();
        app.setStatus(1);
        List<App> appList = thirdAppMapper.selectThirdApp(app);
        for (App app : appList)
        {
            redisUtil.set(app.getAppId(), app.getAppSecret());
        }
        log.info("加载appId和appSecret到缓存成功");
    }

app.java

package com.xxx.domain;

import lombok.Data;

/**
 * @Description app实体
 * @Author liqinglong
 * @DateTime 2024-01-16 17:19
 * @Version 1.0
 */
@Data
public class App {
    private String appId;
    private String appSecret;
    private Integer status;
}

注意在实际应用中,每添加或修改appId时都保存到缓存,保存缓存里的appId和appSecret是最新的。

  1. 定义签名注解
    Signature.java
package com.xxx.api.config.openapi.config.sign;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 1. @author liqinglong
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Signature {
}
  1. 签名注解的实现

SignatureAspect.java

package com.xxx.api;

import com.alibaba.fastjson.JSONObject;
import com.xx.api.config.openapi.config.BodyReaderHttpServletRequestWrapper;
import com.xxx.api.config.openapi.config.HttpHelper;
import com.xxx.common.exception.CustomException;
import com.xxx.common.redis.RedisUtil;
import com.xxx.common.utils.SignUtil;
import com.xxx.common.utils.StringUtils;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.*;

/**
 1. @author pdai
 */
@Aspect
@Component
public class SignatureAspect {
    @Autowired
    private RedisUtil redisUtil;
    /**
     * SIGN_HEADER.
     */
    private static final String SIGN_HEADER = "X-SIGN";

    /**
     * pointcut.
     */
    @Pointcut("execution(@com.xxx.api.config.openapi.config.sign.Signature * *(..))")
    private void verifySignPointCut() {
        // nothing
    }

    /**
     * verify sign.
     */
    @Before("verifySignPointCut()")
    public void verify() {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        // 判断请求方式
        String method = request.getMethod();
        if ("POST".equals(method)) {
            // 获取请求Body参数
            try{
                ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
                String body = HttpHelper.getBodyString(requestWrapper);
                String bodyString = URLDecoder.decode(body, "utf-8");
                if (StringUtils.isEmpty(bodyString)) {
                    throw new CustomException("请求参数不能为空");
                }
                // 解析参数转JSON格式
                JSONObject jsonObject = JSONObject.parseObject(bodyString);
                // 验签
                validation(jsonObject);
            } catch (Exception e){
                if(e instanceof CustomException){
                    throw new CustomException(e.getMessage(),((CustomException) e).getCode());
                }
                throw new CustomException("验证签名失败,请稍后重试");
            }
        }
        if ("GET".equals(method)) {
            // 获取请求参数
            Map allRequestParam = getAllRequestParam(request);
            Set<Map.Entry<String, String>> entries = allRequestParam.entrySet();
            // 参数转JSON格式
            JSONObject jsonObject = new JSONObject();
            entries.forEach(key -> {
                jsonObject.put(key.getKey(), key.getValue());
            });
            // 验签
            validation(jsonObject);
        }
    }

    /**
     * 验签
     *
     * @param body     请求参数
     * @return
     * @throws IOException
     */
    private boolean validation(JSONObject body){
        //请求签名
        String sign = body.getString("sign");
        body.remove("sign");
        String appId = body.getString("appId");
        if (StringUtils.isEmpty(appId)) {
            throw new CustomException("应用id不能为空");
        }
        String appSecret = (String)redisUtil.get(appId);
        if (StringUtils.isEmpty(appSecret)) {
            throw new CustomException("应用id不存在或应用处于禁用状态");
        }
        //根据APPID查询密钥进行重签
        String sign1 = SignUtil.getSign(body,appSecret);
        // 校验签名
        if (!StringUtils.equals(sign1, sign)) {
            throw new CustomException("签名错误");
        }
        return true;
    }

    /**
     * 获取客户端GET请求中所有的请求参数
     *
     * @param request
     * @return
     */
    private Map getAllRequestParam(final HttpServletRequest request) {
        Map res = new HashMap();
        Enumeration temp = request.getParameterNames();
        if (null != temp) {
            while (temp.hasMoreElements()) {
                String en = (String) temp.nextElement();
                String value = request.getParameter(en);
                res.put(en, value);
                //如果字段的值为空,判断若值为空,则删除这个字段>
                if (null == res.get(en) || "".equals(res.get(en))) {
                    res.remove(en);
                }
            }
        }
        return res;
    }
}
  1. 在接口方法上加注解@Signature实现接入验证
/**
     * @Description 验证查询用户是不
     * @param jsonObject
     * @return com.hw.common.web.AjaxResult
     */
    @Signature
    @PostMapping(value = "/checkIsBlank")
    public AjaxResult checkIsBlank(@RequestBody JSONObject jsonObject) {
        return AjaxResult.success(airCustBlanklistService.checkIsBlank(jsonObject));
    }

客户端

  1. 数据传输说明

通讯协议:HTTP
请求方式:POST
请求报文格式:JSON
请求头部:“Content-Type” 必须设置为application/json;charset=UTF-8

  1. 接口接入
    2.1 申请appId和appSecret

向平台申请appId和appSecret。
appId:应用唯一标识,每个客户端唯一
appSecret:应用密钥,用于签名
成功申请后,得到appId和appSecret如下:
appId: 3BTN5hc5
appSecret: 63f1824265b58f88352e22c0d5e7967ed98ce726

2.2 签名方式

平台给客户端提供appId和appSecret,客户端需保证appSecret保密性。
平台同时提供签名工具类:SignUtil.java,供客户端生成签名。

SignUtil.java 代码:


import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.*;

@Slf4j
public class SignUtil {

    /**
     * 请求参数签名
     *
     * @param params 请求参数
     * @return 签名
     */
    public static String getSign(Map<String, Object> params, String key) {
        if (params == null) {
            params = new HashMap<>();
        }
        String str = jointParams(sortMapByKey(params), key);
        return SecureUtil.md5(str).toUpperCase();
    }

    /**
     * Map按key ASCII 进行排序
     *
     * @param map 待排序map
     * @return 排序完成的map
     */
    private static Map<String, Object> sortMapByKey(Map<String, Object> map) {
        if (map.isEmpty()) {
            return map;
        }
        Map<String, Object> sortMap = new TreeMap<>(Comparator.naturalOrder());
        sortMap.putAll(map);
        return sortMap;
    }


    /**
     * 把map 拼接成key=value& 格式,并且处理了相关参数
     *
     * @param params
     * @return
     */
    private static String jointParams(Map<String, Object> params, String key) {
        StringBuilder contrastSign = new StringBuilder();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            String entryKey = entry.getKey();
            Object entryValue = entry.getValue();

            if (entryKey != null && entryValue != null) {
                if (entryValue instanceof Map || entryValue instanceof List) {
                    char[] chs = JSONObject.toJSONString(entryValue, SerializerFeature.WriteMapNullValue).toCharArray();
                    Arrays.sort(chs);
                    entryValue = new String(chs);
                }
                if (entryValue instanceof String) {
                    if (StringUtils.isEmpty(entryValue.toString())){
                        continue;
                    }
                }
                contrastSign
                        .append(entryKey)
                        .append("=")
                        .append(entryValue)
                        .append("&");
            }
        }
        contrastSign.append("key=").append(key);
        return contrastSign.toString();
    }

}

2.2.1 SignUtil.java 的依赖

<dependency>
   <groupId>cn.hutool</groupId>
   <artifactId>hutool-all</artifactId>
   <version>5.7.13</version>
</dependency>

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
</dependency>

2.2.3 示例
调用平台的接口:验证是否被列为黑名单接口 /checkIsBlank
封装请求参数

 //1.设置请求参数
        Map<String, Object> params = new HashMap<>();
        params.put("customerName","贵州太极云科技有限公司");
        params.put("idNumber","xxx");
        params.put("contactPhone","138xxxx9024");
        //2.获取AppId和AppSecret,根据实际情况配置,这里的数据为测试数据
        Map<String, Object> publicParams = new HashMap<>();
        publicParams.put("AppId","AX5aA3U8");
        publicParams.put("AppSecret","16a83104651b7463bec74064a6e46b5e07b9a45e");
        JSONObject accountParams = new JSONObject(publicParams);
        //3.签名并封装公共请求参数,此方法为固定调用即可
        params = AirportDataUtil.getSignParams(params,accountParams);
        //4.发送post请求调用接口,请根据实际情况配置,这里的数据为测试数据
        String url = "http://localhost:5008/blacklist/checkIsBlank";
        HttpResult httpResult = HttpUtils.doJsonPost(url,params);
        if (httpResult.getCode() == 200){
            //6.获取响应数据,根据实际情况处理
            JSONObject body = JSON.parseObject(httpResult.getData());
            if (body.containsKey("code") && body.getInteger("code") >= 400){
                //7.根据实际情况处理异常
                throw new CustomException(body.getString("msg"));
            }
            System.out.println("获取响应数据:"+body);
        }
        

签名并封装请求参数类
AirportDataUtil.java

public class AirportDataUtil {
    public static Map<String,Object> getSignParams(Map<String,Object> params, JSONObject accountParams){
        String appId = accountParams.get("AppId") == null?null:accountParams.getString("AppId");
        String appSecret = accountParams.get("AppSecret") == null?null:accountParams.getString("AppSecret");
        if (StringUtils.isEmpty(appId) || StringUtils.isEmpty(appSecret)){
            throw new CustomException("渠道接口密钥不存在");
        }

        params.put("appId",appId);
        String sign = SignUtil.getSign(params,appSecret);
        params.put("sign",sign);
        return params;
    }
}

工具类HttpUtils.java

import com.alibaba.fastjson.JSONObject;
import com.shop.cereshop.commons.domain.express.HttpResult;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;

import java.io.IOException;


public class HttpUtils {

    private final static String CHARSET_DEFAULT = "UTF-8";
 

    /**
     * post请求  编码格式默认application/json
     *
     * @param url     请求url
     * @return
     */
    public static HttpResult doJsonPost(String url, Object obj) {
        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        CloseableHttpResponse resp = null;

        HttpResult result = new HttpResult();
        try {
            HttpPost httpPost = new HttpPost(url);
            httpPost.addHeader("Content-type", "application/json; charset=utf-8");
            httpPost.setHeader("Accept", "application/json");
            httpPost.setEntity(new StringEntity(JSONObject.toJSONString(obj), CHARSET_DEFAULT));

            resp = httpClient.execute(httpPost);
            String body = EntityUtils.toString(resp.getEntity(), CHARSET_DEFAULT);
            int statusCode = resp.getStatusLine().getStatusCode();
            result.setStatus(statusCode);
            result.setBody(body);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != resp) {
                try {
                    resp.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }
}

返回结果实体类HttpResult.java

import lombok.Data;

/**
 * http请求接口返回结果实体
 */
@Data
public class HttpResult {
    private int code;
    private Object data;
    private String msg;

    public HttpResult() {
    }

    public HttpResult(int code, Object data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }
}

应答Json

{
  "msg": "操作成功",
  "code": 200,
  "data": true
}
  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: BC260Y是一款支持NB-IoT网络的物联网模组,能够连接物联网设备与电信网络,实现设备之间的信息传输与通信。而AE平台(Application Enablement Platform)是电信公司提供的一种开放式平台,可为物联网研发者和服务商提供云计算、数据分析、应用开发等全方位支持,帮助用户快速开发和部署物联网应用。BC260Y接入电信AE平台后,用户可以利用AE平台提供的各种云服务,创建多种智能应用,从而实现对物联网设备的远程管理和监控。比如,可以通过云平台实时监控设备运行状态,或对设备进行简单的配置和命令下发。 此外,BC260Y通过接入AE平台,还可以实现设备的数据采集、数据存储、数据分析等多种功能,可在物联网应用中提供更为完整的数据支持,进一步丰富物联网应用场景。通过电信AE平台的智能分析和处理能力,用户可以快速定位设备故障,对设备进行优化,有效提升系统的效率和可靠性,进而推动物联网应用的发展与应用。总之,BC260Y接入电信AE平台的优势显而易见,有机会带来更多创新性和便利性的物联网应用。 ### 回答2: BC260y是一种智能物联网设备,可以通过其内置的无线芯片,与电信的AEP平台进行连接。AEP平台是电信推出的一款物联网应用解决方案,主要用于监测物联网设备的使用情况,远程管理设备和数据处理等功能。 为了将BC260y设备接入AEP平台,需要进行以下步骤: 第一步,需要在AEP平台上创建一个设备接入规则,以便网关可以将设备数据发送到AEP平台上。 第二步,需要为BC260y设备配置相应的网络参数,包括IP地址、子网掩码、网关地址等,以及设备的标识信息,以便AEP平台可以识别该设备。 第三步,根据AEP平台的接口文档,将设备数据的格式进行解析并打包,然后发送到AEP平台上,完成信息的上报。 第四步,根据AEP平台提供的文档,编写相应的应用程序,进行数据处理和设备管理等操作。 通过这些步骤,就可以将BC260y设备成功接入电信的AEP平台实现设备监控、管理和远程控制等功能,为物联网应用的开发和运营提供了有力支持。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值