事需身历,再去言之有物。

起一个SIP服务和起一个Netty的服务本质上是差不多,都是起服务用作网络通信。

GB28181的注册流程牵扯用户认证,是安防通讯安全方面的一个亮点。

注册流程

注册流程描述:

  1. 设备向服务器发送 Register请求;

  2. 服务器向设备发送响应401,并在响应的消息头 WWW_Authenticate字段中给出适合设备的认证体制和参数;

  3. 设备重新向服务器发送 Register请求,在请求的 Authorization字段给出信任书, 包含认证信息;

  4. 服务器对请求进行验证,如果检查出 设备身份合法,向设备发送成功响应 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) ``#usernamerealm 在字段 “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.
    */
   @SuppressWarnings("deprecation")
   public SipLayer(String username, String ip, int port) throws PeerUnavailableException,
           TransportNotSupportedException, InvalidArgumentException, ObjectInUseException, TooManyListenersException {
       setUsername(username);
       sipFactory = SipFactory.getInstance();
       sipFactory.setPathName("gov.nist");
       Properties properties = new Properties();
       properties.setProperty("javax.sip.STACK_NAME", "cameraReg");
       properties.setProperty("javax.sip.IP_ADDRESS", ip);

       /**
        * sip_server_log.log 和 sip_debug_log.log
        * public static final int TRACE_NONE = 0;
        public static final int TRACE_MESSAGES = 16;
        public static final int TRACE_EXCEPTION = 17;
        public static final int TRACE_DEBUG = 32;
        */
       properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "16");
       properties.setProperty("gov.nist.javax.sip.SERVER_LOG", "sip_server_log");
       properties.setProperty("gov.nist.javax.sip.DEBUG_LOG", "sip_debug_log");

       sipStack = sipFactory.createSipStack(properties);
       headerFactory = sipFactory.createHeaderFactory();
       addressFactory = sipFactory.createAddressFactory();
       messageFactory = sipFactory.createMessageFactory();

       ListeningPoint tcp = sipStack.createListeningPoint(port, "tcp");
       ListeningPoint udp = sipStack.createListeningPoint(port, "udp");

       sipProvider = sipStack.createSipProvider(tcp);
       sipProvider.addSipListener(this);
       sipProvider = sipStack.createSipProvider(udp);
       sipProvider.addSipListener(this);
  }

   /**
    * This method uses the SIP stack to send a message. 第一个参数:用户名 第二个参数:IP地址
    * 第三个参数:消息内容
    */
   public void sendMessage(String username, String address, String message)
           throws ParseException, InvalidArgumentException, SipException {

       SipURI from = addressFactory.createSipURI(getUsername(), getHost() + ":" + getPort());
       Address fromNameAddress = addressFactory.createAddress(from);
       fromNameAddress.setDisplayName(getUsername());
       FromHeader fromHeader = headerFactory.createFromHeader(fromNameAddress, "cameraReg1.0");

       SipURI toAddress = addressFactory.createSipURI(username, address);
       Address toNameAddress = addressFactory.createAddress(toAddress);
       toNameAddress.setDisplayName(username);
       ToHeader toHeader = headerFactory.createToHeader(toNameAddress, null);

       SipURI requestURI = addressFactory.createSipURI(username, address);
       requestURI.setTransportParam("udp");

       ArrayList<ViaHeader> viaHeaders = new ArrayList<ViaHeader>();
       ViaHeader viaHeader = headerFactory.createViaHeader(getHost(), getPort(), "udp", "branch1");
       viaHeaders.add(viaHeader);

       CallIdHeader callIdHeader = sipProvider.getNewCallId();

       @SuppressWarnings("deprecation")
       CSeqHeader cSeqHeader = headerFactory.createCSeqHeader(1, Request.MESSAGE);

       MaxForwardsHeader maxForwards = headerFactory.createMaxForwardsHeader(70);

       Request request = messageFactory.createRequest(requestURI, Request.MESSAGE, callIdHeader, cSeqHeader,
               fromHeader, toHeader, viaHeaders, maxForwards);

       SipURI contactURI = addressFactory.createSipURI(getUsername(), getHost());
       contactURI.setPort(getPort());
       Address contactAddress = addressFactory.createAddress(contactURI);
       contactAddress.setDisplayName(getUsername());
       ContactHeader contactHeader = headerFactory.createContactHeader(contactAddress);
       request.addHeader(contactHeader);

       ContentTypeHeader contentTypeHeader = headerFactory.createContentTypeHeader("text", "plain");
       request.setContent(message, contentTypeHeader);

       sipProvider.sendRequest(request);
  }

   /**
    * This method is called by the SIP stack when a response arrives.
    */
   public void processResponse(ResponseEvent evt) {
       Response response = evt.getResponse();
       int status = response.getStatusCode();

       if ((status >= 200) && (status < 300)) { // Success!
           messageProcessor.processInfo("--Sent");
           return;
      }

       messageProcessor.processError("Previous message not sent: " + status);
  }

   /**
    * SIP服务端接收消息的方法
    * Content 里面是GBK编码
    * This method is called by the SIP stack when a new request arrives.
    */
   public void processRequest(RequestEvent evt) {
       Request req = evt.getRequest();
       messageProcessor.processMessage(req, messageFactory, sipProvider);
  }


   /**
    * This method is called by the SIP stack when there's no answer to a
    * message. Note that this is treated differently from an error message.
    */
   public void processTimeout(TimeoutEvent evt) {
       messageProcessor.processError("Previous message not sent: " + "timeout");
  }

   /**
    * This method is called by the SIP stack when there's an asynchronous
    * message transmission error.
    */
   public void processIOException(IOExceptionEvent evt) {
       messageProcessor.processError("Previous message not sent: " + "I/O Exception");
  }

   /**
    * This method is called by the SIP stack when a dialog (session) ends.
    */
   public void processDialogTerminated(DialogTerminatedEvent evt) {
  }

   /**
    * This method is called by the SIP stack when a transaction ends.
    */
   public void processTransactionTerminated(TransactionTerminatedEvent evt) {
  }

   public String getHost() {
       return sipStack.getIPAddress();
  }

   @SuppressWarnings("deprecation")
   public int getPort() {
       return sipProvider.getListeningPoint().getPort();
  }

   public String getUsername() {
       return username;
  }

   public void setUsername(String newUsername) {
       username = newUsername;
  }

   public MessageProcessor getMessageProcessor() {
       return messageProcessor;
  }

   public void setMessageProcessor(MessageProcessor newMessageProcessor) {
       messageProcessor = newMessageProcessor;
  }
}
MessageProcessor 接口
package org.example.interfaces;

import javax.sip.SipProvider;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;

/**
* @Description 消息处理回调函数接口
* @Author LH
* @Date 2022/11/2 9:55
**/
public interface MessageProcessor {
   /**
    * 接收IPCamera发来的SIP协议消息的时候产生的回调函数
    */
   void processMessage(Request req, MessageFactory messageFactory, SipProvider sipProvider);

   void processError(String errorMessage);

   void processInfo(String infoMessage);
}
MessageProcessor实现类
package org.example.interfaces.impl;

import org.example.interfaces.MessageProcessor;

import javax.sip.SipProvider;
import javax.sip.message.MessageFactory;
import javax.sip.message.Request;

/**
* @Description 消息处理回调函数接口实现
* @Author LH
* @Date 2022/11/2 10:03
**/
public class MessageProcessorImpl implements MessageProcessor {
   @Override
   public void processMessage(Request req, MessageFactory messageFactory, SipProvider sipProvider) {

  }

   @Override
   public void processError(String errorMessage) {

  }

   @Override
   public void processInfo(String infoMessage) {

  }
}

实现类可以使用工厂模式和策略模式进行细分,对于不同的请求方法,使用不同的实现类进行接收处理响应,下篇文章做展开。

 

参考:https://my.oschina.net/u/2338224/blog/2907136?share_token=B51D8023-1CFD-48EB-9A0B-1077FFA5D0DF&tt_from=dingtalk&utm_source=dingtalk&utm_medium=toutiao_ios&utm_campaign=client_share&dtshare_count=1

 

原文地址:http://www.cnblogs.com/l12138h/p/16850814.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性