GA/T 1400协议 - 注册注销流程

需求:作为下级平台将数据推送至上级平台。第一步需要下级平台向上级平台进行注册/注销。

目录

  • 一、准备材料:
  • 二、注册/注销接口
    • 1.接口文档
    • 2.入参对象
    • 3. 注册/注销请求方法体示例:
  • 三、注册/注销流程:
    • 注册/注销请求一共需要发送两次:
      • 第一次请求响应401返回值
      • 第二次请求鉴权请求头示例
  • 三、注册注销代码示例
    • 注册请求成功示例
    • 注销请求成功示例
  • 四、摘要加密工具类
  • 参考文章

一、准备材料:

(1)获取上级平台提供的:用户名、授权码、访问地址。
(2)提供下级平台的平台编码:20位数字编码。(规则详见:协议GB/T 28181 附录D.1编码规则A)

二、注册/注销接口

1.接口文档

GA/T 1400协议 - 注册注销流程_第1张图片

2.入参对象

GA/T 1400协议 - 注册注销流程_第2张图片

GA/T 1400协议 - 注册注销流程_第3张图片

3. 注册/注销请求方法体示例:

/**
 *  GA/T 1400 注册请求方法体
 */
@Data
public class RegisterRequestObject {
	
	private RegisterObject RegisterObject;
	
    @Data
	public static class RegisterObject {
		private String DeviceID;	// 20位数字平台编码
	}
}

=====================================================================

JSON字符串:"{\"RegisterObject\":{\"DeviceID\":\"20位数字平台编码\"}}";

三、注册/注销流程:

GA/T 1400协议 - 注册注销流程_第4张图片

注册/注销请求一共需要发送两次:

(1)第一次请求头中不带Authorization鉴权信息,获得401响应和第二次请求所需的请求头信息。
(2)将第一次请求返回的请求头信息,经过加密放入第二次请求的请求头Authorization中,再次发起注册/注销请求。

第一次请求响应401返回值

HTTP/1.1 401 Unauthorized
WWW-Authenticate
Digest realm="viid",qop="auth",nonce="d492c06a91164fafb77f2741bb7640b0"

第二次请求鉴权请求头示例

Authorization = 
Digest username="system",	// 上级平台提供的用户名
realm="viid", 	// 认证域,第一次注册返回
qop="auth", 	// 保护质量,第一次注册返回
nonce="d492c06a91164fafb77f2741bb7640b0", 	// 服务器随机串,第一次注册返回
uri="/VIID/System/Register",
cnonce="248a286a6dda379e", 	// 客户端随机串
nc="00000001", 	// 16进制请求认证计数器,第一次是1
response="c87fbd6aa4083d9784224abe8cc3e509"	// 客户端根据算法算出的摘要值

三、注册注销代码示例

 /**
 * GA/T1400 注册注销 demo
 */
@Slf4j
public class Gat1400RegisterDemo {

    // 上级平台访问地址
    private final static String ip = "http://xx.xx.xx.xx:xxxx";

	// 注册接口
    private final static String registerUri = "/VIID/System/Register";

	// 注销接口
    private final static String unRegisterUri = "/VIID/System/UnRegister";

    /**
     * 注册消息
     * 注册成功返回请求头Authorization
     * @return
     */
    public static String register() {
        String result = "0";

        try {
            // 注册计数器
            List<String> countList = new ArrayList<String>();
            countList.add("count");

            // 本级平台编码:40000000000000000000
            String registerParamStr  = "{\"RegisterObject\":{\"DeviceID\":\"40000000000000000000\"}}";

            // 第一次注册请求响应结果
            boolean firstResponse = false;
            Map<String, String> firstResponeMap = null;
            try {
                // 获得第一次注册请求返回的参数Map
                firstResponeMap = firstRegister(ip + registerUri, "POST", registerParamStr);
                String statusCode = firstResponeMap.get("statusCode");
                if ("401".equals(statusCode)) {
                    firstResponse = true;
                }
            } catch (Exception e) {
            	result = "1";
                return result;
            }

            // 进行第二次请求
            boolean secondResponse = false;
            if (firstResponse) {
                // 第一次注册,获取响应头中的验证信息,构造鉴权请求头
                String Authorization = setAuthorization(firstResponeMap, countList, registerUri);
                String responseStr2 = secondRegister(ip + registerUri, "POST", registerParamStr, Authorization);
                
                // 成功响应200 ok
                if ("200".equals(responseStr2)) {
                    secondResponse = true;
                }
                return Authorization;

            } else {
                result = "2";
                return result;
            }

        } catch (Exception e) {
            return result;
        }
    }

	 /**
     * 注销
     * @param args
     */
    public static String unRegister() {
        String result = "0";
       
        try {
            // 注销计数器
            List<String> countList = new ArrayList<String>();
            countList.add("count");
           
            // 本级平台编码:20位数字编码
            String sendJson = "{\"UnRegisterObject\":{\"DeviceID\":\"40000000000000000000\"}}";
            
            boolean firstResponse = false;    // 判断第一次响应是否成功
            Map<String, String> firstResponseMap = null;

            try {
                firstResponseMap = firstRegister(ip + unRegisterUri, "POST", sendJson);
                String statusCode = map_FirstReturn.get("statusCode");
                if ("401".equals(statusCode)) {
                    firstResponse = true;
                }
            } catch (Exception e) {
				result = "1";
                return result;
            }

            // 进行第二次请求
            boolean secondResponse= false;
            if (firstResponse ) {
                // 第一次注销,获取响应头中的验证信息
                String Authorization = setAuthorization(map_FirstReturn, countList, uri);
                String responseStr2 = secondRegister(ip + unRegisterUri, "POST", sendJson, Authorization);

                // 成功响应200 ok
                if ("200".equals(responseStr2)) {
                    secondResponse= true;
                }

                return Authorization;
            } else {
            	result = "2";
                return result;
            }

        } catch (Exception e) {
            return result;
        }
    }

	/**
	* 第一次注册请求
	* 将返回的参数保存到Map
	*/
    public static Map<String, String> firstRegister(String url, String method, String param) throws IOException {
    
        Map<String, String> returnMap = new HashMap<>();
        URL restUrl = new URL(url);
        HttpURLConnection conn = (HttpURLConnection) restUrl.openConnection();
        conn.setDoOutput(true);
        conn.setDoInput(true);
        conn.setRequestMethod(method);
        conn.setUseCaches(false);
        conn.setRequestProperty("Accept-Charset", "UTF-8");

        // 设置访问提交模式,表单提交
        conn.setRequestProperty("Content-Type", "application/VIID+JSON;charset=UTF-8");

        // 补充表头
        conn.setRequestProperty("Accept", "application/json,application/*+json");
        conn.setRequestProperty("Connection", "keepalive");
        conn.setRequestProperty("Content-Length", "65");
        conn.setRequestProperty("User-Identify", "40000000000000000000");   // 平台编码
        conn.setRequestProperty("User-Agent", "libghttp/1.0");

        conn.setConnectTimeout(5000); //连接超时 单位毫秒
        conn.setReadTimeout(5000);  // 读取超时 单位毫秒

        // 传递参数
        if (param != null) {
            byte[] bytes = param.toString().getBytes(StandardCharsets.UTF_8);
            conn.getOutputStream().write(bytes);
        }

        // 将响应的参数放入Map
        Map<String, List<String>> map = conn.getHeaderFields();
        returnMap.put("statusCode", conn.getResponseCode() + "");

        for (String key : map.keySet()) {
            if (map.get(key) != null) {
                if ("WWW-Authenticate".equals(key)) {
                    String responStr = map.get(key).get(0).replaceAll("Digest", "").replaceAll(" ", "");
                    String[] arr = responStr.split(",");
                    for (int i = 0; i < arr.length; i++) {
                        if (arr[i].contains("realm")) {
                            String realm = arr[i].replaceAll("realm=", "").replaceAll("\"", "");
                            returnMap.put("realm", realm);
                        } else if (arr[i].contains("qop=")) {
                            String qop = arr[i].replaceAll("qop=", "").replaceAll("\"", "");
                            returnMap.put("qop", qop);
                        } else if (arr[i].contains("nonce=")) {
                            String nonce = arr[i].replaceAll("nonce=", "").replaceAll("\"", "");
                            returnMap.put("nonce", nonce);
                        }
                    }
                }
            }
        }
        return returnMap;
    }

    /**
     * 构造鉴权请求头Authorization 
     * 
     * Authorization 请求头字段
     * response: 客户端根据算法算出的摘要值
     * username: 要认证的用户名
     * realm: 认证域,可取任意标识值
     * uri: 请求的资源位置
     * qop: 保护质量
     * nonce: 服务器密码随机数
     * nc: 16进制请求认证计数器,第一次 00000001
     * algorithm: 默认MD5算法
     * cnonce: 客户端密码随机数
     * @param map_FirstReturn
     * @param countList
     * @return
     */
    private static String setAuthorization(Map<String, String> firstResponseMap, List<String> countList, String uri) {

        // HA1:HD:HA2
        String username = "system";
        String pwd = "systempwd";
        String realm = firstResponseMap.get("realm");
        String nonce = firstResponseMap.get("nonce");
        String qop = firstResponseMap.get("qop");

        String noncecount = getHex(countList.size());
        String cnonce = RandomUtils.getRandomString(16); // 随机串
        String method = "POST";
        String algorithm = "MD5";

        // 根据算法求出摘要
        String response = Digests.http_da_calc_HA1(username, realm, pwd, nonce, noncecount, cnonce, qop, method, uri, null);

        // 拼接请求头
        String Authorization = "Digest ";
        Authorization += "username=\"" + username + "\",";
        Authorization+="realm=\""+realm+"\", ";
        Authorization+="qop=\""+qop+"\", ";
        Authorization+="nonce=\""+nonce+"\", ";
        Authorization+="uri=\""+uri+"\", ";
        Authorization+="algorithm=\""+algorithm+"\", ";
        Authorization+="cnonce=\""+cnonce+"\", ";
        Authorization+="nc=\""+noncecount+"\", ";
        Authorization+="response=\""+response+"\"";

        return Authorization;
    }

    /**
     * 第二次注册请求
     * 携带鉴权请求头 Authorization
     * @return
     */
    public static  String secondRegister(String url, String method, String param, String authentication) throws IOException {

        URL restUrl = new URL(url);
        HttpURLConnection conn = (HttpURLConnection) restUrl.openConnection();
        conn.setDoOutput(true);
        conn.setDoInput(true);
        conn.setRequestMethod(method);
        conn.setUseCaches(false);

        // 设置访问提交模式:表单提交
        conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
        conn.setRequestProperty("Authorization", authentication);
        // 补充表头
        conn.setRequestProperty("Accept", "application/json,application/*+json");
        conn.setRequestProperty("Connection", "keepalive");
        conn.setRequestProperty("Content-Length", "65");
        conn.setRequestProperty("User-Identify", "40000000000000000000");   // 平台编码
        conn.setRequestProperty("User-Agent", "libghttp/1.0");

        conn.setConnectTimeout(5000);  // 连接超时 单位毫秒
        conn.setReadTimeout(5000);      // 读取超时 单位毫秒

        log.info("2-conn:{}", conn);
        // 传递参数
        if (param != null && !"".equals(param)) {
            byte[] bytes = param.toString().getBytes(StandardCharsets.UTF_8);
            conn.getOutputStream().write(bytes);
        }

        InputStream inputStream = conn.getInputStream();
        byte[] buffer = new byte[10240];
        int length = 0;
        StringBuilder strResult = new StringBuilder();
        while ((length = inputStream.read(buffer)) != -1) {
            strResult.append(new String(buffer, 0, length, StandardCharsets.UTF_8));
        }

        return conn.getResponseCode() + "";
    }

	 private static String getHex(int size) {
        String hex = Integer.toHexString(size);
        if (hex.length() < 8) {
            int l = 8 - hex.length();
            StringBuilder zero = new StringBuilder();
            for (int i = 0; i < l; i++) {
                zero.append("0");
            }
            hex = zero + hex;
        }
        return hex;
    }

    public static void main(String[] args) {
        register();
    }
}

注册请求成功示例

{"ResponseStatusObject":{"RequestURL":"/VIID/System/Register","StatusCode":0,"StatusString":"注册成功","Id":"40000000000000000000","LocalTime":"20230329184022"}}

注销请求成功示例

{"ResponseStatusObject":{"RequestURL":"/VIID/System/UnRegister","StatusCode":0,"StatusString":"注销成功","Id":"40000000000000000000","LocalTime":"20230329172625"}}

四、摘要加密工具类

/**
 * Request-Digest 摘要计算过程
 */

public class Digests {

    private static SecureRandom random = new SecureRandom();
    /**
     * 加密遵循RFC2671规范 将相关参数加密生成一个MD5字符串,并返回
     * Authorization 请求头字段
     *      * response: 客户端根据算法算出的摘要值
     *      * username: 要认证的用户名
     *      * realm: 认证域,可取任意标识值
     *      * uri: 请求的资源位置
     *      * qop: 保护质量
     *      * nonce: 服务器密码随机数
     *      * nc: 16进制请求认证计数器,第一次 00000001
     *      * algorithm: 默认MD5算法
     *      * cnonce: 客户端密码随机数
     */
    public static String http_da_calc_HA1(String username, String realm, String password,
                                          String nonce, String nc, String cnonce, String qop,
                                          String method, String uri, String algorithm) {
        String HA1, HA2;
        if ("MD5-sess".equals(algorithm)) {
            HA1 = HA1_MD5_sess(username, realm, password, nonce, cnonce);
        } else {
            HA1 = HA1_MD5(username, realm, password);
        }
        byte[] md5Byte = md5(HA1.getBytes());
        HA1 = new String(Hex.encodeHex(md5Byte));

        md5Byte = md5(HA2(method, uri).getBytes());
        HA2 = new String(Hex.encodeHex(md5Byte));

        String original = HA1 + ":" + (nonce + ":" + nc + ":" + cnonce + ":" + qop) + ":" + HA2;

        md5Byte = md5(original.getBytes());
        return new String(Hex.encodeHex(md5Byte));

    }

    /**
     * algorithm值为MD5时规则
     */
    private static String HA1_MD5(String username, String realm, String password) {
        return username + ":" + realm + ":" + password;
    }

    /**
     * algorithm值为MD5-sess时规则
     */
    private static String HA1_MD5_sess(String username, String realm, String password, String nonce, String cnonce) {
        //      MD5(username:realm:password):nonce:cnonce

        String s = HA1_MD5(username, realm, password);
        byte[] md5Byte = md5(s.getBytes());
        String smd5 = new String(Hex.encodeHex(md5Byte));

        return smd5 + ":" + nonce + ":" + cnonce;
    }

    private static String HA2(String method, String uri) {
        return method + ":" + uri;
    }

    /**
     * 对输入字符串进行md5散列.
     */
    public static byte[] md5(byte[] input) {
        return digest(input, "MD5", null, 1);
    }

    /**
     * 对字符串进行散列, 支持md5与sha1算法.
     */
    private static byte[] digest(byte[] input, String algorithm, byte[] salt, int iterations) {
        try {
            MessageDigest digest = MessageDigest.getInstance(algorithm);

            if (salt != null) {
                digest.update(salt);
            }

            byte[] result = digest.digest(input);

            for (int i = 1; i < iterations; i++) {
                digest.reset();
                result = digest.digest(result);
            }
            return result;
        } catch (GeneralSecurityException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 随机生成numBytes长度数组
     * @param numBytes
     * @return
     */
    public static byte[] generateSalt(int numBytes) {
        Validate.isTrue(numBytes > 0, "numBytes argument must be a positive integer (1 or larger)", (long)numBytes);
        byte[] bytes = new byte[numBytes];
        random.nextBytes(bytes);
        return bytes;
    }

    @Deprecated
    public static String generateSalt2(int length) {
        String val = "";
        Random random = new Random();
        //参数length,表示生成几位随机数
        for(int i = 0; i < length; i++) {
            String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
            //输出字母还是数字
            if( "char".equalsIgnoreCase(charOrNum) ) {
                //输出是大写字母还是小写字母
                int temp = random.nextInt(2)%2 == 0 ? 65 : 97;
                val += (char)(random.nextInt(26) + temp);
            } else if( "num".equalsIgnoreCase(charOrNum) ) {
                val += String.valueOf(random.nextInt(10));
            }
        }
        return val.toLowerCase();
    }


    public static void main(String[] args) throws UnsupportedEncodingException {
         String s = http_da_calc_HA1("username", "realm", "pwd",
                                     "nonce", "nc", "cnonce", "qop",
                                     "method", "uri", "algorithm");

    }

参考文章

GAT1400的注册流程RFC2617认证
GA-T1400协议–注册注销
GAT1400注册和保活及上下级关系
GA/T 1400.1-4

你可能感兴趣的:(工作日常,GA/T,1400,java,spring)