From f3e551b3b9460591072a572f0210a553357eb8e1 Mon Sep 17 00:00:00 2001 From: xiongziliang <771730766@qq.com> Date: Sun, 10 Dec 2017 01:34:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8F=AF=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=8C=96=E7=9A=84RTSP=E7=99=BB=E5=BD=95=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Common/config.cpp | 7 + src/Common/config.h | 11 ++ src/Rtsp/Rtsp.h | 7 +- src/Rtsp/RtspSession.cpp | 279 ++++++++++++++++++++++++++++++++++----- src/Rtsp/RtspSession.h | 16 +++ tests/test_server.cpp | 60 ++++++++- 6 files changed, 344 insertions(+), 36 deletions(-) diff --git a/src/Common/config.cpp b/src/Common/config.cpp index 86e157a9..5f380f44 100644 --- a/src/Common/config.cpp +++ b/src/Common/config.cpp @@ -52,6 +52,9 @@ const char kBroadcastRtspSrcRegisted[] = "kBroadcastRtspSrcRegisted"; const char kBroadcastRtmpSrcRegisted[] = "kBroadcastRtmpSrcRegisted"; const char kBroadcastRecordMP4[] = "kBroadcastRecordMP4"; const char kBroadcastHttpRequest[] = "kBroadcastHttpRequest"; +const char kBroadcastOnGetRtspRealm[] = "kBroadcastOnGetRtspRealm"; +const char kBroadcastOnRtspAuth[] = "kBroadcastOnRtspAuth"; + } //namespace Broadcast //代理失败最大重试次数 @@ -174,9 +177,13 @@ const char kPort[] = RTSP_FIELD"port"; #define RTSP_SERVER_NAME "ZLServer" const char kServerName[] = RTSP_FIELD"serverName"; +const char kAuthBasic[] = RTSP_FIELD"authBasic"; + onceToken token([](){ mINI::Instance()[kPort] = RTSP_PORT; mINI::Instance()[kServerName] = RTSP_SERVER_NAME; + //默认Md5方式认证 + mINI::Instance()[kAuthBasic] = 0; },nullptr); } //namespace Rtsp diff --git a/src/Common/config.h b/src/Common/config.h index def1326b..defa2ef8 100644 --- a/src/Common/config.h +++ b/src/Common/config.h @@ -72,6 +72,15 @@ extern const char kBroadcastRecordMP4[]; extern const char kBroadcastHttpRequest[]; #define BroadcastHttpRequestArgs const Parser &parser,HttpSession::HttpResponseInvoker &invoker,bool &consumed +//该流是否需要认证?是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证 +extern const char kBroadcastOnGetRtspRealm[]; +#define BroadcastOnGetRtspRealmArgs const char *app,const char *stream,const RtspSession::onGetRealm &invoker + +//请求认证用户密码事件,user_name为用户名,must_no_encrypt如果为true,则必须提供明文密码(因为此时是base64认证方式),否则会导致认证失败 +//获取到密码后请调用invoker并输入对应类型的密码和密码类型,invoker执行时会匹配密码 +extern const char kBroadcastOnRtspAuth[]; +#define BroadcastOnRtspAuthArgs const char *user_name,bool must_no_encrypt,const RtspSession::onAuth &invoker + } //namespace Broadcast //代理失败最大重试次数 @@ -118,6 +127,8 @@ namespace Rtsp { extern const char kServerName[]; extern const char kPort[]; +//是否优先base64方式认证?默认Md5方式认证 +extern const char kAuthBasic[]; } //namespace Rtsp ////////////RTMP服务器配置/////////// diff --git a/src/Rtsp/Rtsp.h b/src/Rtsp/Rtsp.h index 44ace8df..9b033553 100644 --- a/src/Rtsp/Rtsp.h +++ b/src/Rtsp/Rtsp.h @@ -186,20 +186,21 @@ public: return ret; } - static StrCaseMap parseArgs(const string &str){ + static StrCaseMap parseArgs(const string &str,const char *pair_delim = "&", const char *key_delim = "="){ StrCaseMap ret; - auto arg_vec = split(str, "&"); + auto arg_vec = split(str, pair_delim); for (string &key_val : arg_vec) { if (!key_val.size()) { continue; } - auto key_val_vec = split(key_val, "="); + auto key_val_vec = split(key_val, key_delim); if (key_val_vec.size() >= 2) { ret[key_val_vec[0]] = key_val_vec[1]; } } return ret; } + private: string m_strMethod; string m_strUrl; diff --git a/src/Rtsp/RtspSession.cpp b/src/Rtsp/RtspSession.cpp index a640ba9d..0da8d5f2 100644 --- a/src/Rtsp/RtspSession.cpp +++ b/src/Rtsp/RtspSession.cpp @@ -30,6 +30,7 @@ #include "RtspSession.h" #include "Device/base64.h" #include "Util/mini.h" +#include "Util/MD5.h" #include "Util/onceToken.h" #include "Util/TimeTicker.h" #include "Util/NoticeCenter.h" @@ -192,33 +193,255 @@ bool RtspSession::handleReq_Options() { bool RtspSession::handleReq_Describe() { m_strUrl = m_parser.Url(); - if (m_strSession.size() != 0) { - //会话id这时还没生成,这个逻辑可以注释以提高响应速度 - return false; - } if (!findStream()) { //未找到相应的MediaSource send_StreamNotFound(); return false; } -//回复sdp - int n = sprintf(m_pcBuf, "RTSP/1.0 200 OK\r\n" - "CSeq: %d\r\n" - "Server: %s-%0.2f(build in %s)\r\n" - "%s" - "x-Accept-Retransmit: our-retransmit\r\n" - "x-Accept-Dynamic-Rate: 1\r\n" - "Content-Base: %s/\r\n" - "Content-Type: application/sdp\r\n" - "Content-Length: %d\r\n\r\n%s", - m_iCseq, g_serverName.data(), - RTSP_VERSION, RTSP_BUILDTIME, - dateHeader().data(), m_strUrl.data(), - (int) m_strSdp.length(), m_strSdp.data()); - send(m_pcBuf, n); - return true; + + weak_ptr weakSelf = dynamic_pointer_cast(shared_from_this()); + //该请求中的认证信息 + auto authorization = m_parser["Authorization"]; + onGetRealm invoker = [weakSelf,authorization](const string &realm){ + if(realm.empty()){ + //无需认证,回复sdp + onAuthSuccess(weakSelf); + return; + } + //该流需要认证 + onAuthUser(weakSelf,realm,authorization); + }; + + //广播是否需要认证事件 + if(!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnGetRtspRealm,m_strApp.data(),m_strStream.data(),invoker)){ + //无人监听此事件,说明无需认证 + invoker(""); + } + return true; +} +void RtspSession::onAuthSuccess(const weak_ptr &weakSelf) { + auto strongSelf = weakSelf.lock(); + if(!strongSelf){ + //本对象已销毁 + return; + } + strongSelf->async([weakSelf](){ + auto strongSelf = weakSelf.lock(); + if(!strongSelf){ + //本对象已销毁 + return; + } + char response[2 * 1024]; + int n = sprintf(response, + "RTSP/1.0 200 OK\r\n" + "CSeq: %d\r\n" + "Server: %s-%0.2f(build in %s)\r\n" + "%s" + "x-Accept-Retransmit: our-retransmit\r\n" + "x-Accept-Dynamic-Rate: 1\r\n" + "Content-Base: %s/\r\n" + "Content-Type: application/sdp\r\n" + "Content-Length: %d\r\n\r\n%s", + strongSelf->m_iCseq, strongSelf->g_serverName.data(), + RTSP_VERSION, RTSP_BUILDTIME, + dateHeader().data(), strongSelf->m_strUrl.data(), + (int) strongSelf->m_strSdp.length(), strongSelf->m_strSdp.data()); + strongSelf->send(response, n); + }); +} +void RtspSession::onAuthFailed(const weak_ptr &weakSelf,const string &realm) { + auto strongSelf = weakSelf.lock(); + if(!strongSelf){ + //本对象已销毁 + return; + } + strongSelf->async([weakSelf,realm]() { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) { + //本对象已销毁 + return; + } + + int n; + char response[2 * 1024]; + static bool authBasic = mINI::Instance()[Config::Rtsp::kAuthBasic]; + if (!authBasic) { + //我们需要客户端优先以md5方式认证 + strongSelf->m_strNonce = makeRandStr(32); + n = sprintf(response, + "RTSP/1.0 401 Unauthorized\r\n" + "CSeq: %d\r\n" + "Server: %s-%0.2f(build in %s)\r\n" + "%s" + "WWW-Authenticate:Digest realm=\"%s\",nonce=\"%s\"\r\n\r\n", + strongSelf->m_iCseq, strongSelf->g_serverName.data(), + RTSP_VERSION, RTSP_BUILDTIME, + dateHeader().data(), realm.data(), strongSelf->m_strNonce.data()); + }else { + //当然我们也支持base64认证,但是我们不建议这样做 + n = sprintf(response, + "RTSP/1.0 401 Unauthorized\r\n" + "CSeq: %d\r\n" + "Server: %s-%0.2f(build in %s)\r\n" + "%s" + "WWW-Authenticate:Basic realm=\"%s\"\r\n\r\n", + strongSelf->m_iCseq, strongSelf->g_serverName.data(), + RTSP_VERSION, RTSP_BUILDTIME, + dateHeader().data(), realm.data()); + } + strongSelf->send(response, n); + }); } +void RtspSession::onAuthBasic(const weak_ptr &weakSelf,const string &realm,const string &strBase64){ + //base64认证 + char user_pwd_buf[512]; + av_base64_decode((uint8_t *)user_pwd_buf,strBase64.data(),strBase64.size()); + auto user_pwd_vec = Parser::split(user_pwd_buf,":"); + if(user_pwd_vec.size() < 2){ + //认证信息格式不合法,回复401 Unauthorized + onAuthFailed(weakSelf,realm); + return; + } + auto user = user_pwd_vec[0]; + auto pwd = user_pwd_vec[1]; + onAuth invoker = [pwd,realm,weakSelf](bool encrypted,const string &good_pwd){ + if(!encrypted && pwd == good_pwd){ + //提供的是明文密码且匹配正确 + onAuthSuccess(weakSelf); + }else{ + //密码错误 + onAuthFailed(weakSelf,realm); + } + }; + //此时必须提供明文密码 + bool must_no_encrypt = true; + if(!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnRtspAuth,user.data(),must_no_encrypt,invoker)){ + //表明该流需要认证却没监听请求密码事件,这一般是大意的程序所为,警告之 + WarnL << "请监听kBroadcastOnRtspAuth事件!"; + //但是我们还是忽略认证以便完成播放 + //我们输入的密码是明文 + invoker(false,pwd); + } +} +static string trim(string str){ + while(!str.empty()){ + if(str.front()==' ' || str.front()=='"'){ + str.erase(0,1); + continue; + } + break; + } + while(!str.empty()){ + if(str.back()==' ' || str.back()=='"'){ + str.pop_back(); + continue; + } + break; + } + return str; +} +void RtspSession::onAuthDigest(const weak_ptr &weakSelf,const string &realm,const string &strMd5){ + auto strongSelf = weakSelf.lock(); + if(!strongSelf){ + return; + } + + DebugL << strMd5; + auto mapTmp = Parser::parseArgs(strMd5,",","="); + decltype(mapTmp) map; + for(auto &pr : mapTmp){ + map[trim(pr.first)] = trim(pr.second); + } + //check realm + if(realm != map["realm"]){ + TraceL << "realm not mached:" << realm << "," << map["realm"]; + onAuthFailed(weakSelf,realm); + return ; + } + //check nonce + auto nonce = map["nonce"]; + if(strongSelf->m_strNonce != nonce){ + TraceL << "nonce not mached:" << nonce << "," << strongSelf->m_strNonce; + onAuthFailed(weakSelf,realm); + return ; + } + //check username and uri + auto username = map["username"]; + auto uri = map["uri"]; + auto response = map["response"]; + if(username.empty() || uri.empty() || response.empty()){ + TraceL << "username/uri/response empty:" << username << "," << uri << "," << response; + onAuthFailed(weakSelf,realm); + return ; + } + + auto realInvoker = [weakSelf,realm,nonce,uri,username,response](bool ignoreAuth,bool encrypted,const string &good_pwd){ + if(ignoreAuth){ + //忽略认证 + onAuthSuccess(weakSelf); + TraceL << "auth ignored"; + return; + } + /* + response计算方法如下: + RTSP客户端应该使用username + password并计算response如下: + (1)当password为MD5编码,则 + response = md5( password:nonce:md5(public_method:url) ); + (2)当password为ANSI字符串,则 + response= md5( md5(username:realm:password):nonce:md5(public_method:url) ); + */ + auto encrypted_pwd = good_pwd; + if(!encrypted){ + //提供的是明文密码 + encrypted_pwd = MD5(username+ ":" + realm + ":" + good_pwd).hexdigest(); + } + + auto good_response = MD5( encrypted_pwd + ":" + nonce + ":" + MD5(string("DESCRIBE") + ":" + uri).hexdigest()).hexdigest(); + if(strcasecmp(good_response.data(),response.data()) == 0){ + //认证成功!md5不区分大小写 + onAuthSuccess(weakSelf); + TraceL << "onAuthSuccess"; + }else{ + //认证失败! + onAuthFailed(weakSelf,realm); + TraceL << "onAuthFailed"; + } + }; + onAuth invoker = [realInvoker](bool encrypted,const string &good_pwd){ + realInvoker(false,encrypted,good_pwd); + }; + + //此时可以提供明文或md5加密的密码 + bool must_no_encrypt = false; + if(!NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastOnRtspAuth,username.data(),must_no_encrypt,invoker)){ + //表明该流需要认证却没监听请求密码事件,这一般是大意的程序所为,警告之 + WarnL << "请监听kBroadcastOnRtspAuth事件!"; + //但是我们还是忽略认证以便完成播放 + realInvoker(true,true,""); + } +} + +void RtspSession::onAuthUser(const weak_ptr &weakSelf,const string &realm,const string &authorization){ + //请求中包含认证信息 + auto authType = FindField(authorization.data(),NULL," "); + auto authStr = FindField(authorization.data()," ",NULL); + if(authType.empty() || authStr.empty()){ + //认证信息格式不合法,回复401 Unauthorized + onAuthFailed(weakSelf,realm); + return; + } + if(authType == "Basic"){ + //base64认证,需要明文密码 + onAuthBasic(weakSelf,realm,authStr); + }else if(authType == "Digest"){ + //md5认证 + onAuthDigest(weakSelf,realm,authStr); + }else{ + //其他认证方式?不支持! + onAuthFailed(weakSelf,realm); + } +} inline void RtspSession::send_StreamNotFound() { int n = sprintf(m_pcBuf, "RTSP/1.0 404 Stream Not Found\r\n" "CSeq: %d\r\n" @@ -593,17 +816,13 @@ inline void RtspSession::send_NotAcceptable() { send(m_pcBuf, n); } - +void RtspSession::splitRtspUrl(const string &url,string &app,string &stream){ + string strHost = FindField(url.data(), "://", "/"); + app = FindField(url.data(), (strHost + "/").data(), "/"); + stream = FindField(url.data(), (strHost + "/" + app + "/").data(), NULL); +} inline bool RtspSession::findStream() { - - string strHost = FindField(m_strUrl.data(), "://", "/"); - m_strApp = FindField(m_strUrl.data(), (strHost + "/").data(), "/"); - m_strStream = FindField(m_strUrl.data(), (strHost + "/" + m_strApp + "/").data(), NULL); - - auto iPos = m_strStream.find('?'); - if(iPos != string::npos ){ - m_strStream.erase(iPos); - } + splitRtspUrl(m_strUrl,m_strApp,m_strStream); RtspMediaSource::Ptr pMediaSrc = RtspMediaSource::find(m_strApp,m_strStream); if (!pMediaSrc) { WarnL << "No such stream:" << m_strApp << " " << m_strStream; diff --git a/src/Rtsp/RtspSession.h b/src/Rtsp/RtspSession.h index c1b85031..5bfd4562 100644 --- a/src/Rtsp/RtspSession.h +++ b/src/Rtsp/RtspSession.h @@ -52,6 +52,11 @@ class RtspSession; class RtspSession: public TcpLimitedSession { public: typedef std::shared_ptr Ptr; + typedef std::function onGetRealm; + //encrypted为true是则表明是md5加密的密码,否则是明文密码 + //在请求明文密码时如果提供md5密码者则会导致认证失败 + typedef std::function onAuth; + RtspSession(const std::shared_ptr &pTh, const Socket::Ptr &pSock); virtual ~RtspSession(); void onRecv(const Socket::Buffer::Ptr &pBuf) override; @@ -83,6 +88,7 @@ private: void inline send_UnsupportedTransport(); //不支持的传输模式 void inline send_SessionNotFound(); //会话id错误 void inline send_NotAcceptable(); //rtsp同时播放数限制 + void splitRtspUrl(const string &url,string &app,string &stream); inline bool findStream(); //根据rtsp url查找 MediaSource实例 inline void initSender(const std::shared_ptr &pSession); //处理rtsp over http,quicktime使用的 @@ -107,6 +113,13 @@ private: inline void onRcvPeerUdpData(int iTrackIdx, const Socket::Buffer::Ptr &pBuf, const struct sockaddr &addr); inline void startListenPeerUdpData(); + //认证相关 + static void onAuthSuccess(const weak_ptr &weakSelf); + static void onAuthFailed(const weak_ptr &weakSelf,const string &realm); + static void onAuthUser(const weak_ptr &weakSelf,const string &realm,const string &authorization); + static void onAuthBasic(const weak_ptr &weakSelf,const string &realm,const string &strBase64); + static void onAuthDigest(const weak_ptr &weakSelf,const string &realm,const string &strMd5); + char *m_pcBuf = nullptr; Ticker m_ticker; Parser m_parser; //rtsp解析类 @@ -143,6 +156,9 @@ private: bool m_bListenPeerUdpData = false; RtpBroadCaster::Ptr m_pBrdcaster; + //登录认证 + string m_strNonce; + //RTSP over HTTP function m_onDestory; bool m_bBase64need = false; //是否需要base64解码 diff --git a/tests/test_server.cpp b/tests/test_server.cpp index c3f9d301..461274a0 100644 --- a/tests/test_server.cpp +++ b/tests/test_server.cpp @@ -33,6 +33,7 @@ #include "Rtmp/RtmpSession.h" #include "Http/HttpSession.h" #include "Shell/ShellSession.h" +#include "Util/MD5.h" #ifdef ENABLE_OPENSSL #include "Util/SSLBox.h" @@ -57,6 +58,57 @@ using namespace ZL::Shell; using namespace ZL::Thread; using namespace ZL::Network; + +#define REALM "realm_zlmedaikit" + +static onceToken s_token([](){ + NoticeCenter::Instance().addListener(nullptr,Config::Broadcast::kBroadcastOnGetRtspRealm,[](BroadcastOnGetRtspRealmArgs){ + if(string("1") == stream ){ + // live/1需要认证 + EventPoller::Instance().async([invoker](){ + //该流需要认证,并且设置realm + invoker(REALM); + }); + }else{ + //我们异步执行invoker。 + //有时我们要查询redis或数据库来判断该流是否需要认证,通过invoker的方式可以做到完全异步 + EventPoller::Instance().async([invoker](){ + //该流我们不需要认证 + invoker(""); + }); + } + }); + + NoticeCenter::Instance().addListener(nullptr,Config::Broadcast::kBroadcastOnRtspAuth,[](BroadcastOnRtspAuthArgs){ + InfoL << "用户:" << user_name << (must_no_encrypt ? " Base64" : " MD5" )<< " 方式登录"; + string user = user_name; + //假设我们异步读取数据库 + EventPoller::Instance().async([must_no_encrypt,invoker,user](){ + if(user == "test0"){ + //假设数据库保存的是明文 + invoker(false,"pwd0"); + return; + } + + if(user == "test1"){ + //假设数据库保存的是密文 + auto encrypted_pwd = MD5(user + ":" + REALM + ":" + "pwd1").hexdigest(); + invoker(true,encrypted_pwd); + return; + } + if(user == "test2" && must_no_encrypt){ + //假设登录的是test2,并且以base64方式登录,此时我们提供加密密码,那么会导致认证失败 + //可以通过这个方式屏蔽base64这种不安全的加密方式 + invoker(true,"pwd2"); + return; + } + + //其他用户密码跟用户名一致 + invoker(false,user); + }); + }); +}, nullptr); + int main(int argc,char *argv[]){ //设置退出信号处理函数 signal(SIGINT, [](int){EventPoller::Instance().shutdown();}); @@ -68,8 +120,9 @@ int main(int argc,char *argv[]){ //这里是拉流地址,支持rtmp/rtsp协议,负载必须是H264+AAC //如果是其他不识别的音视频将会被忽略(譬如说h264+adpcm转发后会去除音频) auto urlList = {"rtmp://live.hkstv.hk.lxdns.com/live/hks", + "rtmp://live.hkstv.hk.lxdns.com/live/hks" //rtsp链接支持输入用户名密码 - "rtsp://admin:jzan123456@192.168.0.122/"}; + /*"rtsp://admin:jzan123456@192.168.0.122/"*/}; map proxyMap; int i=0; for(auto &url : urlList){ @@ -82,13 +135,14 @@ int main(int argc,char *argv[]){ //http://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4 //rtsp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4 //rtmp://127.0.0.1/record/live/0/2017-04-11/11-09-38.mp4 - PlayerProxy::Ptr player(new PlayerProxy("live",to_string(i++).data())); + PlayerProxy::Ptr player(new PlayerProxy("live",to_string(i).data())); //指定RTP over TCP(播放rtsp时有效) (*player)[RtspPlayer::kRtpType] = PlayerBase::RTP_TCP; //开始播放,如果播放失败或者播放中止,将会自动重试若干次,重试次数在配置文件中配置,默认一直重试 player->play(url); //需要保存PlayerProxy,否则作用域结束就会销毁该对象 - proxyMap.emplace(url,player); + proxyMap.emplace(to_string(i),player); + ++i; } #ifdef ENABLE_OPENSSL