mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-29 14:45:55 +08:00
支持可配置化的RTSP登录认证
This commit is contained in:
parent
02aba328c1
commit
f3e551b3b9
@ -52,6 +52,9 @@ const char kBroadcastRtspSrcRegisted[] = "kBroadcastRtspSrcRegisted";
|
|||||||
const char kBroadcastRtmpSrcRegisted[] = "kBroadcastRtmpSrcRegisted";
|
const char kBroadcastRtmpSrcRegisted[] = "kBroadcastRtmpSrcRegisted";
|
||||||
const char kBroadcastRecordMP4[] = "kBroadcastRecordMP4";
|
const char kBroadcastRecordMP4[] = "kBroadcastRecordMP4";
|
||||||
const char kBroadcastHttpRequest[] = "kBroadcastHttpRequest";
|
const char kBroadcastHttpRequest[] = "kBroadcastHttpRequest";
|
||||||
|
const char kBroadcastOnGetRtspRealm[] = "kBroadcastOnGetRtspRealm";
|
||||||
|
const char kBroadcastOnRtspAuth[] = "kBroadcastOnRtspAuth";
|
||||||
|
|
||||||
} //namespace Broadcast
|
} //namespace Broadcast
|
||||||
|
|
||||||
//代理失败最大重试次数
|
//代理失败最大重试次数
|
||||||
@ -174,9 +177,13 @@ const char kPort[] = RTSP_FIELD"port";
|
|||||||
#define RTSP_SERVER_NAME "ZLServer"
|
#define RTSP_SERVER_NAME "ZLServer"
|
||||||
const char kServerName[] = RTSP_FIELD"serverName";
|
const char kServerName[] = RTSP_FIELD"serverName";
|
||||||
|
|
||||||
|
const char kAuthBasic[] = RTSP_FIELD"authBasic";
|
||||||
|
|
||||||
onceToken token([](){
|
onceToken token([](){
|
||||||
mINI::Instance()[kPort] = RTSP_PORT;
|
mINI::Instance()[kPort] = RTSP_PORT;
|
||||||
mINI::Instance()[kServerName] = RTSP_SERVER_NAME;
|
mINI::Instance()[kServerName] = RTSP_SERVER_NAME;
|
||||||
|
//默认Md5方式认证
|
||||||
|
mINI::Instance()[kAuthBasic] = 0;
|
||||||
},nullptr);
|
},nullptr);
|
||||||
|
|
||||||
} //namespace Rtsp
|
} //namespace Rtsp
|
||||||
|
@ -72,6 +72,15 @@ extern const char kBroadcastRecordMP4[];
|
|||||||
extern const char kBroadcastHttpRequest[];
|
extern const char kBroadcastHttpRequest[];
|
||||||
#define BroadcastHttpRequestArgs const Parser &parser,HttpSession::HttpResponseInvoker &invoker,bool &consumed
|
#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
|
} //namespace Broadcast
|
||||||
|
|
||||||
//代理失败最大重试次数
|
//代理失败最大重试次数
|
||||||
@ -118,6 +127,8 @@ namespace Rtsp {
|
|||||||
|
|
||||||
extern const char kServerName[];
|
extern const char kServerName[];
|
||||||
extern const char kPort[];
|
extern const char kPort[];
|
||||||
|
//是否优先base64方式认证?默认Md5方式认证
|
||||||
|
extern const char kAuthBasic[];
|
||||||
} //namespace Rtsp
|
} //namespace Rtsp
|
||||||
|
|
||||||
////////////RTMP服务器配置///////////
|
////////////RTMP服务器配置///////////
|
||||||
|
@ -186,20 +186,21 @@ public:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static StrCaseMap parseArgs(const string &str){
|
static StrCaseMap parseArgs(const string &str,const char *pair_delim = "&", const char *key_delim = "="){
|
||||||
StrCaseMap ret;
|
StrCaseMap ret;
|
||||||
auto arg_vec = split(str, "&");
|
auto arg_vec = split(str, pair_delim);
|
||||||
for (string &key_val : arg_vec) {
|
for (string &key_val : arg_vec) {
|
||||||
if (!key_val.size()) {
|
if (!key_val.size()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto key_val_vec = split(key_val, "=");
|
auto key_val_vec = split(key_val, key_delim);
|
||||||
if (key_val_vec.size() >= 2) {
|
if (key_val_vec.size() >= 2) {
|
||||||
ret[key_val_vec[0]] = key_val_vec[1];
|
ret[key_val_vec[0]] = key_val_vec[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
string m_strMethod;
|
string m_strMethod;
|
||||||
string m_strUrl;
|
string m_strUrl;
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "RtspSession.h"
|
#include "RtspSession.h"
|
||||||
#include "Device/base64.h"
|
#include "Device/base64.h"
|
||||||
#include "Util/mini.h"
|
#include "Util/mini.h"
|
||||||
|
#include "Util/MD5.h"
|
||||||
#include "Util/onceToken.h"
|
#include "Util/onceToken.h"
|
||||||
#include "Util/TimeTicker.h"
|
#include "Util/TimeTicker.h"
|
||||||
#include "Util/NoticeCenter.h"
|
#include "Util/NoticeCenter.h"
|
||||||
@ -192,17 +193,47 @@ bool RtspSession::handleReq_Options() {
|
|||||||
|
|
||||||
bool RtspSession::handleReq_Describe() {
|
bool RtspSession::handleReq_Describe() {
|
||||||
m_strUrl = m_parser.Url();
|
m_strUrl = m_parser.Url();
|
||||||
if (m_strSession.size() != 0) {
|
|
||||||
//会话id这时还没生成,这个逻辑可以注释以提高响应速度
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!findStream()) {
|
if (!findStream()) {
|
||||||
//未找到相应的MediaSource
|
//未找到相应的MediaSource
|
||||||
send_StreamNotFound();
|
send_StreamNotFound();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//回复sdp
|
|
||||||
int n = sprintf(m_pcBuf, "RTSP/1.0 200 OK\r\n"
|
weak_ptr<RtspSession> weakSelf = dynamic_pointer_cast<RtspSession>(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<RtspSession> &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"
|
"CSeq: %d\r\n"
|
||||||
"Server: %s-%0.2f(build in %s)\r\n"
|
"Server: %s-%0.2f(build in %s)\r\n"
|
||||||
"%s"
|
"%s"
|
||||||
@ -211,14 +242,206 @@ bool RtspSession::handleReq_Describe() {
|
|||||||
"Content-Base: %s/\r\n"
|
"Content-Base: %s/\r\n"
|
||||||
"Content-Type: application/sdp\r\n"
|
"Content-Type: application/sdp\r\n"
|
||||||
"Content-Length: %d\r\n\r\n%s",
|
"Content-Length: %d\r\n\r\n%s",
|
||||||
m_iCseq, g_serverName.data(),
|
strongSelf->m_iCseq, strongSelf->g_serverName.data(),
|
||||||
RTSP_VERSION, RTSP_BUILDTIME,
|
RTSP_VERSION, RTSP_BUILDTIME,
|
||||||
dateHeader().data(), m_strUrl.data(),
|
dateHeader().data(), strongSelf->m_strUrl.data(),
|
||||||
(int) m_strSdp.length(), m_strSdp.data());
|
(int) strongSelf->m_strSdp.length(), strongSelf->m_strSdp.data());
|
||||||
send(m_pcBuf, n);
|
strongSelf->send(response, n);
|
||||||
return true;
|
});
|
||||||
|
}
|
||||||
|
void RtspSession::onAuthFailed(const weak_ptr<RtspSession> &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<RtspSession> &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<RtspSession> &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<RtspSession> &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() {
|
inline void RtspSession::send_StreamNotFound() {
|
||||||
int n = sprintf(m_pcBuf, "RTSP/1.0 404 Stream Not Found\r\n"
|
int n = sprintf(m_pcBuf, "RTSP/1.0 404 Stream Not Found\r\n"
|
||||||
"CSeq: %d\r\n"
|
"CSeq: %d\r\n"
|
||||||
@ -593,17 +816,13 @@ inline void RtspSession::send_NotAcceptable() {
|
|||||||
send(m_pcBuf, n);
|
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() {
|
inline bool RtspSession::findStream() {
|
||||||
|
splitRtspUrl(m_strUrl,m_strApp,m_strStream);
|
||||||
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);
|
|
||||||
}
|
|
||||||
RtspMediaSource::Ptr pMediaSrc = RtspMediaSource::find(m_strApp,m_strStream);
|
RtspMediaSource::Ptr pMediaSrc = RtspMediaSource::find(m_strApp,m_strStream);
|
||||||
if (!pMediaSrc) {
|
if (!pMediaSrc) {
|
||||||
WarnL << "No such stream:" << m_strApp << " " << m_strStream;
|
WarnL << "No such stream:" << m_strApp << " " << m_strStream;
|
||||||
|
@ -52,6 +52,11 @@ class RtspSession;
|
|||||||
class RtspSession: public TcpLimitedSession<MAX_TCP_SESSION> {
|
class RtspSession: public TcpLimitedSession<MAX_TCP_SESSION> {
|
||||||
public:
|
public:
|
||||||
typedef std::shared_ptr<RtspSession> Ptr;
|
typedef std::shared_ptr<RtspSession> Ptr;
|
||||||
|
typedef std::function<void(const string &realm)> onGetRealm;
|
||||||
|
//encrypted为true是则表明是md5加密的密码,否则是明文密码
|
||||||
|
//在请求明文密码时如果提供md5密码者则会导致认证失败
|
||||||
|
typedef std::function<void(bool encrypted,const string &pwd_or_md5)> onAuth;
|
||||||
|
|
||||||
RtspSession(const std::shared_ptr<ThreadPool> &pTh, const Socket::Ptr &pSock);
|
RtspSession(const std::shared_ptr<ThreadPool> &pTh, const Socket::Ptr &pSock);
|
||||||
virtual ~RtspSession();
|
virtual ~RtspSession();
|
||||||
void onRecv(const Socket::Buffer::Ptr &pBuf) override;
|
void onRecv(const Socket::Buffer::Ptr &pBuf) override;
|
||||||
@ -83,6 +88,7 @@ private:
|
|||||||
void inline send_UnsupportedTransport(); //不支持的传输模式
|
void inline send_UnsupportedTransport(); //不支持的传输模式
|
||||||
void inline send_SessionNotFound(); //会话id错误
|
void inline send_SessionNotFound(); //会话id错误
|
||||||
void inline send_NotAcceptable(); //rtsp同时播放数限制
|
void inline send_NotAcceptable(); //rtsp同时播放数限制
|
||||||
|
void splitRtspUrl(const string &url,string &app,string &stream);
|
||||||
inline bool findStream(); //根据rtsp url查找 MediaSource实例
|
inline bool findStream(); //根据rtsp url查找 MediaSource实例
|
||||||
|
|
||||||
inline void initSender(const std::shared_ptr<RtspSession> &pSession); //处理rtsp over http,quicktime使用的
|
inline void initSender(const std::shared_ptr<RtspSession> &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 onRcvPeerUdpData(int iTrackIdx, const Socket::Buffer::Ptr &pBuf, const struct sockaddr &addr);
|
||||||
inline void startListenPeerUdpData();
|
inline void startListenPeerUdpData();
|
||||||
|
|
||||||
|
//认证相关
|
||||||
|
static void onAuthSuccess(const weak_ptr<RtspSession> &weakSelf);
|
||||||
|
static void onAuthFailed(const weak_ptr<RtspSession> &weakSelf,const string &realm);
|
||||||
|
static void onAuthUser(const weak_ptr<RtspSession> &weakSelf,const string &realm,const string &authorization);
|
||||||
|
static void onAuthBasic(const weak_ptr<RtspSession> &weakSelf,const string &realm,const string &strBase64);
|
||||||
|
static void onAuthDigest(const weak_ptr<RtspSession> &weakSelf,const string &realm,const string &strMd5);
|
||||||
|
|
||||||
char *m_pcBuf = nullptr;
|
char *m_pcBuf = nullptr;
|
||||||
Ticker m_ticker;
|
Ticker m_ticker;
|
||||||
Parser m_parser; //rtsp解析类
|
Parser m_parser; //rtsp解析类
|
||||||
@ -143,6 +156,9 @@ private:
|
|||||||
bool m_bListenPeerUdpData = false;
|
bool m_bListenPeerUdpData = false;
|
||||||
RtpBroadCaster::Ptr m_pBrdcaster;
|
RtpBroadCaster::Ptr m_pBrdcaster;
|
||||||
|
|
||||||
|
//登录认证
|
||||||
|
string m_strNonce;
|
||||||
|
|
||||||
//RTSP over HTTP
|
//RTSP over HTTP
|
||||||
function<void(void)> m_onDestory;
|
function<void(void)> m_onDestory;
|
||||||
bool m_bBase64need = false; //是否需要base64解码
|
bool m_bBase64need = false; //是否需要base64解码
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
#include "Rtmp/RtmpSession.h"
|
#include "Rtmp/RtmpSession.h"
|
||||||
#include "Http/HttpSession.h"
|
#include "Http/HttpSession.h"
|
||||||
#include "Shell/ShellSession.h"
|
#include "Shell/ShellSession.h"
|
||||||
|
#include "Util/MD5.h"
|
||||||
|
|
||||||
#ifdef ENABLE_OPENSSL
|
#ifdef ENABLE_OPENSSL
|
||||||
#include "Util/SSLBox.h"
|
#include "Util/SSLBox.h"
|
||||||
@ -57,6 +58,57 @@ using namespace ZL::Shell;
|
|||||||
using namespace ZL::Thread;
|
using namespace ZL::Thread;
|
||||||
using namespace ZL::Network;
|
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[]){
|
int main(int argc,char *argv[]){
|
||||||
//设置退出信号处理函数
|
//设置退出信号处理函数
|
||||||
signal(SIGINT, [](int){EventPoller::Instance().shutdown();});
|
signal(SIGINT, [](int){EventPoller::Instance().shutdown();});
|
||||||
@ -68,8 +120,9 @@ int main(int argc,char *argv[]){
|
|||||||
//这里是拉流地址,支持rtmp/rtsp协议,负载必须是H264+AAC
|
//这里是拉流地址,支持rtmp/rtsp协议,负载必须是H264+AAC
|
||||||
//如果是其他不识别的音视频将会被忽略(譬如说h264+adpcm转发后会去除音频)
|
//如果是其他不识别的音视频将会被忽略(譬如说h264+adpcm转发后会去除音频)
|
||||||
auto urlList = {"rtmp://live.hkstv.hk.lxdns.com/live/hks",
|
auto urlList = {"rtmp://live.hkstv.hk.lxdns.com/live/hks",
|
||||||
|
"rtmp://live.hkstv.hk.lxdns.com/live/hks"
|
||||||
//rtsp链接支持输入用户名密码
|
//rtsp链接支持输入用户名密码
|
||||||
"rtsp://admin:jzan123456@192.168.0.122/"};
|
/*"rtsp://admin:jzan123456@192.168.0.122/"*/};
|
||||||
map<string , PlayerProxy::Ptr> proxyMap;
|
map<string , PlayerProxy::Ptr> proxyMap;
|
||||||
int i=0;
|
int i=0;
|
||||||
for(auto &url : urlList){
|
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
|
//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
|
//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
|
//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时有效)
|
//指定RTP over TCP(播放rtsp时有效)
|
||||||
(*player)[RtspPlayer::kRtpType] = PlayerBase::RTP_TCP;
|
(*player)[RtspPlayer::kRtpType] = PlayerBase::RTP_TCP;
|
||||||
//开始播放,如果播放失败或者播放中止,将会自动重试若干次,重试次数在配置文件中配置,默认一直重试
|
//开始播放,如果播放失败或者播放中止,将会自动重试若干次,重试次数在配置文件中配置,默认一直重试
|
||||||
player->play(url);
|
player->play(url);
|
||||||
//需要保存PlayerProxy,否则作用域结束就会销毁该对象
|
//需要保存PlayerProxy,否则作用域结束就会销毁该对象
|
||||||
proxyMap.emplace(url,player);
|
proxyMap.emplace(to_string(i),player);
|
||||||
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ENABLE_OPENSSL
|
#ifdef ENABLE_OPENSSL
|
||||||
|
Loading…
Reference in New Issue
Block a user