需求:作为下级平台将数据推送至上级平台。第一步需要下级平台向上级平台进行注册/注销。
(1)获取上级平台提供的:用户名、授权码、访问地址。
(2)提供下级平台的平台编码:20位数字编码。(规则详见:协议GB/T 28181 附录D.1编码规则A)
/**
* GA/T 1400 注册请求方法体
*/
@Data
public class RegisterRequestObject {
private RegisterObject RegisterObject;
@Data
public static class RegisterObject {
private String DeviceID; // 20位数字平台编码
}
}
=====================================================================
JSON字符串:"{\"RegisterObject\":{\"DeviceID\":\"20位数字平台编码\"}}";
(1)第一次请求头中不带Authorization鉴权信息,获得401响应和第二次请求所需的请求头信息。
(2)将第一次请求返回的请求头信息,经过加密放入第二次请求的请求头Authorization中,再次发起注册/注销请求。
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