Java实现SIP的GB28181注册 - 整体有个概念(二)
起一个SIP服务和起一个Netty的服务本质上是差不多,都是起服务用作网络通信。
GB28181
的注册流程牵扯用户认证,是安防通讯安全方面的一个亮点。
注册流程
注册流程描述:
-
设备向服务器发送 Register请求;
-
服务器向设备发送响应401,
并在响应的消息头 WWW_Authenticate字段中给出适合设备的认证体制和参数
; -
设备重新向服务器发送 Register请求,在请求的 Authorization字段给出信任书, 包含认证信息;
-
服务器对请求进行验证,如果检查出 设备身份合法,向设备发送成功响应 200 OK,如果身份不合法则发送拒绝服务应答。
注册抓包
通过抓包协议Wireshark
抓到的401回复:
Via: SIP/2.0/UDP 172.24.20.109:5060;rport=5060;received=172.24.20.109;branch=z9hG4bK352707374
From: <sip:34020000001320000002@172.24.20.109:5060>;tag=2109371333
To: <sip:34020000001320000002@172.24.20.109:5060>;tag=888
Call-ID: 545122524@172.24.20.109
CSeq: 1 REGISTER
WWW-Authenticate: Digest realm="3402000000",nonce="1677f194104d46aea6c9f8aebe507017"
Content-Length: 0
第二次Register,也就是附带了Auth
字段的报文:
ìKC8¯)à¯Eóßz@@×Ǭm¬ÄÄßyÁREGISTER sip:34020000002000000001@172.24.20.26:5060 SIP/2.0
Via: SIP/2.0/UDP 172.24.20.109:5060;rport;branch=z9hG4bK742316145
Route: <sip:34020000001320000002@172.24.20.26:5060;lr>
From: <sip:34020000001320000002@172.24.20.109:5060>;tag=2109371333
To: <sip:34020000001320000002@172.24.20.109:5060>
Call-ID: 545122524@172.24.20.109
CSeq: 2 REGISTER
Contact: <sip:34020000001320000002@172.24.20.109:5060>
Authorization: Digest username="34020000001320000002", realm="3402000000", nonce="1677f194104d46aea6c9f8aebe507017", uri="sip:34020000002000000001@172.24.20.26:5060", response="dca920f418cecae456bc1566c5ac7da5", algorithm=MD5
Max-Forwards: 70
User-Agent: SIP UAS V2.1.2.438058
Expires: 3600
Content-Length: 0
验证算法
HA1=MD5 (username:realm:passwd) ``#username
和 realm
在字段 “Authorization”
中可以找到,passwd
这个是由客户端和服务器协商得到的,一般情况下 UAC
端存一个 UAS
也知道的密码就行了。
HA2=MD5 (Method:Uri)
#Method 一般有 INVITE
,ACK
, OPTIONS
, BYE
, CANCEL
, REGISTER
;Uri 可以在字段 “Authorization” 找到。
response = MD5(HA1:nonce:HA2)
关键认证算法的Java代码实现
package org.example;
import static org.example.utils.MD5Utils.md5;
/**
* @Description 服务注册内部流程
* @Author LH
* @Date 2022/11/2 9:46
**/
public class application {
public static void main(String[] args) {
String ha1 = md5("34020000001320000002" + ":" + "3402000000" + ":" + "admin123", ""); //HA1=MD5(username:realm:passwd)
// String ha2 = md5("REGISTER" + ":" + "sip:34020000002000000001@172.24.20.26:5060", ""); //HA2=MD5(Method:Uri)
String ha2 = md5("REGISTER" + ":" + "sip:34020000002000000001@127.0.0.1:5060", ""); //HA2=MD5(Method:Uri)
String response = ha1 + ":" + "326d59f91b6e448fa461fcacd9161abe" + ":" + ha2; // response = MD5(HA1:nonce:HA2)
System.out.println("MD5加密后的字符串为:encodeStr="+md5(response, ""));
}
}
MD5
工具类
package org.example.utils;
import org.apache.commons.codec.digest.DigestUtils;
/**
* @Description MD5工具类
* @Author LH
* @Date 2022/11/2 9:43
**/
public class MD5Utils {
/**
* MD5方法
*
* @param text 明文
* @param key 密钥
* @return 密文
*/
public static String md5(String text, String key) {
//加密后的字符串
return DigestUtils.md5Hex(text + key);
}
/**
* MD5验证方法
*
* @param text 明文
* @param key 密钥
* @param md5 密文
* @return true/false
*/
public static boolean verify(String text, String key, String md5) throws Exception {
//根据传入的密钥进行验证
String md5Text = md5(text, key);
if (md5Text.equalsIgnoreCase(md5)) {
System.out.println("MD5验证通过");
return true;
}
return false;
}
}
注册服务核心代码
MAVEN POM 依赖
<dependencies>
<!-- SPI协议相关的包 -->
<dependency>
<groupId>javax.sip</groupId>
<artifactId>jain-sip-api</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.sip</groupId>
<artifactId>jain-sip-ri</artifactId>
<version>1.2</version>
</dependency>
<!-- MD5编解码 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<!-- 取代log4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.36</version>
</dependency>
<!-- 工具包 -->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.5</version>
</dependency>
</dependencies>
服务启动监听
package org.example.init;
import org.example.entity.SipLayer;
import org.example.interfaces.impl.MessageProcessorImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sip.*;
import java.text.ParseException;
import java.util.TooManyListenersException;
import org.apache.commons.lang.exception.ExceptionUtils;
/**
* @Description 服务启动
* @Author LH
* @Date 2022/11/2 9:50
**/
public class SIPMain {
protected static Logger logger = LoggerFactory.getLogger(SIPMain.class);
public static void main(String[] args) {
run();
}
public static void run() {
//用户名,IP地址,端口
try {
int port = 5060;
SipLayer sipLayer = new SipLayer("admin" , "10.1.7.118" , port); //本地
// SipLayer sipLayer = new SipLayer("admin" , "172.24.20.26" , port); //本地
//SipLayer sipLayer = new SipLayer("admin","xx.xx.xx.xx",port); //阿里云上的IP VECS01532
sipLayer.setMessageProcessor(new MessageProcessorImpl());
System.out.println("服务启动完毕, 已经在"+port+"端口监听消息");
} catch (PeerUnavailableException | TransportNotSupportedException | ObjectInUseException |
InvalidArgumentException | TooManyListenersException e) {
e.printStackTrace();
logger.error(ExceptionUtils.getFullStackTrace(e));
}
}
/**
* 这个方法暂时用不上,目前系统没有需要主动发送消息给SIP终端设备的业务场景
*/
public void sendMsg() throws InvalidArgumentException, TooManyListenersException, ParseException, SipException {
SipLayer sipLayer = new SipLayer("admin","127.0.0.1",5060);
sipLayer.sendMessage(sipLayer.getUsername(), sipLayer.getHost(), "test message");
}
}
SipLayer.java
package org.example.entity;
import org.example.interfaces.MessageProcessor;
import javax.sip.*;
import javax.sip.address.Address;
import javax.sip.address.AddressFactory;
import javax.sip.address.SipURI;
import javax.sip.header.*;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.TooManyListenersException;
/**
* @Description SIP层
* @Author LH
* @Date 2022/11/2 9:53
**/
public class SipLayer implements SipListener {
private MessageProcessor messageProcessor;
private String username;
private SipStack sipStack;
private SipFactory sipFactory;
private AddressFactory addressFactory;
private HeaderFactory headerFactory;
private MessageFactory messageFactory;
private SipProvider sipProvider;
/**
* Here we initialize the SIP stack.
*/