EasyDarwin开源流媒体服务器支持basic基本认证和digest摘要自定义认证
2016-08-26 19:58
399 查看
本文转自EasyDarwin开源团队成员的博客:http://blog.csdn.net/ss00_2012/article/details/52330838
在前面《EasyDarwin拉流支持基本认证和摘要认证》一文中讲述了如何通过修改qtaccess、qtusers来让EasyDarwin对我们创建的用户支持基本认证和摘要认证,之后在与群主的沟通中感觉这种方式的体验性太差,用户的需求是多方面的,可能有的想在配置文件中配置、有的想从数据库中读取、有的想在程序中写死……,我们需要提供一种便于用户自己扩展的方式,而不是与qtaccess、qtusers来打交道。
综上考虑,我们想实现在程序中指定用户名、密码的方式来作为用户后续扩展的一种呈现形式,这样无论用户是想从配置文件获取或者从数据库读取,代码的修改都比较简单。
下面的工作就是讲解如何修改EasyDarwin的代码来实现我们的目的。
使用工具:推流使用EasyPusher_File(下载地址为https://github.com/EasyDarwin/EasyPusher),拉流使用VLC,首先下载EasyDarwin工程代码(下载地址在https://github.com/EasyDarwin/EasyDarwin),当前文档使用的是2016.08.26下载的代码来进行操作的。然后按照文档进行编译运行,在调试之前我们先参考http://blog.csdn.net/cai6811376/article/details/52063666给QTSSAccessModule.cpp文件添加调试信息。之后我们尝试进行推流、拉流发现并没有“用户认证”提示,这是因为EasyDarwin默认的是跳过了对认证的处理,这段代码在RTSPSession::Run函数kRoutingRequest状态的处理中:
可以看到fRequest->SkipAuthorization()的返回值决定了我们是否进行认证,在此我们将fRequest->SkipAuthorization()更改为if(false),然后在QTSSAccessModule.cpp中进行相应的更改:
1.添加默认用户全局变量
以上几个量分别表示用于认证的认证方法、认证领域、用户名、密码、摘要认证密码
2.在Initialize()函数中添加以下语句来计算上面添加的摘要认证密码
这里面根据sAuthRealm、 sUserName、sPassword来计算sDigestPassword,使用了md5运算,需要进行头文件包含#include “md5digest.h”。
3.将AuthenticateRTSPRequest()函数由之前的内容的更改为
之前的代码有很多是读取qtaccess、qtusers的操作,比较繁琐,现在只用判断请求的用户名与我们设置的用户名是否一致,如果一致就将密码(基本认证)/摘要认证密码(摘要认证)写入到用户的theUserProfile对象中,之后在模块外进行密码比较的工作。
4.将AccessAuthorizeRTSPRequest()函数代码更改为
这个函数对功能是对读写权限进行判断,我们现在的更改比较简单,只要用户名密码正确,就拥有所有的读写权限。
好,经过了这些我们在再次运行程序,发现一推流就崩溃,最后定位到QTAccessFile.cpp->AuthorizeRequest->ReadEntireFile函数,这是因为之前在做SDP缓存的时候修改了QTSSModuleUtils::ReadEntireFile的代码,我们在QTSSModuleUtils.cpp中添加如下函数以及在QTSSModuleUtils.h中添加相应的函数声明。
之后将QTAccessFile.cpp->AuthorizeRequest中使用到的ReadEntireFile都改为使用ReadEntireFileEx函数。
再次进行推流测试,发现推流一直返回401,这是因为推送库还没有RTSP认证的功能,我们先不做推流验证。因此我们将前面的认证开关由if(false)更改为if(fRequest->GetMethod() != qtssDescribeMethod),也就说只要是推流就跳过认证。(这个地方不太完美,但还找到完美的方法。)
再次进行推流测试,发现这次可以推流成功;然后我们进行拉流测试发现程序又崩溃了,定位到QTSSAccessLogModule.cpp的LogRequest函数中,在此我们将
更改为
将if (*rtpPacketsSent == 0)更改为if(rtpPacketsSent == NULL || *rtpPacketsSent == 0)
之后再进行测试,发现不会崩溃了,但是一直提示进行RTSP认证,即使我们输入了上面设置的正确用户名和密码还是不行。原来在进行基本认证时不同平台的处理是不一样的,我们通过将如下的代码
我们发现windows下基本验证的时候对密码又进行了md5运算,这不是我们想要的,我们把它改为一致。
再次进行测试,发现还是不行……经排查,发现是对RTSP请求的清理没有到位,将RTSPSession::CleanupRequest由下面代码
更改为下面代码
再次测试,OK!
如有错误,欢迎指正!
WEB:www.EasyDarwin.org
Copyright © EasyDarwin.org 2012-2016
在前面《EasyDarwin拉流支持基本认证和摘要认证》一文中讲述了如何通过修改qtaccess、qtusers来让EasyDarwin对我们创建的用户支持基本认证和摘要认证,之后在与群主的沟通中感觉这种方式的体验性太差,用户的需求是多方面的,可能有的想在配置文件中配置、有的想从数据库中读取、有的想在程序中写死……,我们需要提供一种便于用户自己扩展的方式,而不是与qtaccess、qtusers来打交道。
综上考虑,我们想实现在程序中指定用户名、密码的方式来作为用户后续扩展的一种呈现形式,这样无论用户是想从配置文件获取或者从数据库读取,代码的修改都比较简单。
下面的工作就是讲解如何修改EasyDarwin的代码来实现我们的目的。
使用工具:推流使用EasyPusher_File(下载地址为https://github.com/EasyDarwin/EasyPusher),拉流使用VLC,首先下载EasyDarwin工程代码(下载地址在https://github.com/EasyDarwin/EasyDarwin),当前文档使用的是2016.08.26下载的代码来进行操作的。然后按照文档进行编译运行,在调试之前我们先参考http://blog.csdn.net/cai6811376/article/details/52063666给QTSSAccessModule.cpp文件添加调试信息。之后我们尝试进行推流、拉流发现并没有“用户认证”提示,这是因为EasyDarwin默认的是跳过了对认证的处理,这段代码在RTSPSession::Run函数kRoutingRequest状态的处理中:
if (fRequest->SkipAuthorization()) { // Skip the authentication and authorization states // The foll. normally gets executed at the end of the authorization state // Prepare for kPreprocessingRequest state. fState = kPreprocessingRequest; if (fRequest->GetMethod() == qtssSetupMethod) // Make sure to erase the session ID stored in the request at this point. // If we fail to do so, this same session would be used if another // SETUP was issued on this same TCP connection. fLastRTPSessionIDPtr.Len = 0; else if (fLastRTPSessionIDPtr.Len == 0) fLastRTPSessionIDPtr.Len = ::strlen(fLastRTPSessionIDPtr.Ptr); break; } else fState = kAuthenticatingRequest;
可以看到fRequest->SkipAuthorization()的返回值决定了我们是否进行认证,在此我们将fRequest->SkipAuthorization()更改为if(false),然后在QTSSAccessModule.cpp中进行相应的更改:
1.添加默认用户全局变量
static QTSS_AuthScheme sAuth = qtssAuthDigest; static StrPtrLen sAuthRealm = "EasyDarwin"; static StrPtrLen sUserName = "admin"; static StrPtrLen sPassword = "admin"; static StrPtrLen sDigestPassword;
以上几个量分别表示用于认证的认证方法、认证领域、用户名、密码、摘要认证密码
2.在Initialize()函数中添加以下语句来计算上面添加的摘要认证密码
StrPtrLen strptr; CalcMD5HA1(&sUserName, &sAuthRealm, &sPassword, &strptr); HashToString((unsigned char *)strptr.Ptr, &sDigestPassword);
这里面根据sAuthRealm、 sUserName、sPassword来计算sDigestPassword,使用了md5运算,需要进行头文件包含#include “md5digest.h”。
3.将AuthenticateRTSPRequest()函数由之前的内容的更改为
QTSS_Error AuthenticateRTSPRequest(QTSS_RTSPAuth_Params* inParams) { QTSS_RTSPRequestObject theRTSPRequest = inParams->inRTSPRequest; UInt32 fileErr; OSMutexLocker locker(sUserMutex); if ((NULL == inParams) || (NULL == inParams->inRTSPRequest)) return QTSS_RequestFailed; // Get the user profile object from the request object QTSS_UserProfileObject theUserProfile = NULL; UInt32 len = sizeof(QTSS_UserProfileObject); QTSS_Error theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqUserProfile, 0, (void*)&theUserProfile, &len); Assert(len == sizeof(QTSS_UserProfileObject)); if (theErr != QTSS_NoErr) return theErr; if (sAuth == qtssAuthNone) { // Get the authentication scheme from the request object len = sizeof(sAuth); theErr = QTSS_GetValue(theRTSPRequest, qtssRTSPReqAuthScheme, 0, (void*)&sAuth, &len); Assert(len == sizeof(sAuth)); if (theErr != QTSS_NoErr) return theErr; } else { theErr = QTSS_SetValue(theRTSPRequest, qtssRTSPReqAuthScheme, 0, (void*)&sAuth, sizeof(sAuth)); if (theErr != QTSS_NoErr) return theErr; } (void)QTSS_SetValue(theUserProfile, qtssUserRealm, 0, (void*)(sAuthRealm.Ptr), (sAuthRealm.Len)); // Get the username from the user profile object char* usernameBuf = NULL; theErr = QTSS_GetValueAsString(theUserProfile, qtssUserName, 0, &usernameBuf); OSCharArrayDeleter usernameBufDeleter(usernameBuf); StrPtrLen username(usernameBuf); if (theErr != QTSS_NoErr) return theErr; // No memory is allocated; just a pointer to the profile is returned if(!username.Equal(sUserName))//用户名不相等 { return QTSS_NoErr; } if (sAuth == qtssAuthBasic) (void)QTSS_SetValue(theUserProfile, qtssUserPassword, 0, (void*)(sPassword.Ptr), sPassword.Len); else if (sAuth == qtssAuthDigest) { (void)QTSS_SetValue(theUserProfile, qtssUserPassword, 0, (void*)(sDigestPassword.Ptr), sDigestPassword.Len); } return QTSS_NoErr; }
之前的代码有很多是读取qtaccess、qtusers的操作,比较繁琐,现在只用判断请求的用户名与我们设置的用户名是否一致,如果一致就将密码(基本认证)/摘要认证密码(摘要认证)写入到用户的theUserProfile对象中,之后在模块外进行密码比较的工作。
4.将AccessAuthorizeRTSPRequest()函数代码更改为
QTSS_Error AccessAuthorizeRTSPRequest(QTSS_StandardRTSP_Params* inParams) { Bool16 allowNoAccessFiles = sAllowGuestDefaultEnabled; //no access files allowed means allowing guest access (unknown users) QTSS_ActionFlags noAction = ~qtssActionFlagsRead; // allow any action QTSS_ActionFlags authorizeAction = QTSSModuleUtils::GetRequestActions(inParams->inRTSPRequest); QTSS_Error theErr = QTSS_NoErr; if ( (NULL == inParams) || (NULL == inParams->inRTSPRequest)) return QTSS_RequestFailed; QTSS_RTSPRequestObject theRTSPRequest = inParams->inRTSPRequest; QTSS_UserProfileObject theUserProfile = QTSSModuleUtils::GetUserProfileObject(theRTSPRequest); if (NULL == theUserProfile) return QTSS_RequestFailed; char* username = QTSSModuleUtils::GetUserName_Copy(theUserProfile); OSCharArrayDeleter usernameDeleter(username); Bool16 allowRequest; if(username == NULL) { allowRequest = false; } else { allowRequest = (strcmp(username, sUserName.Ptr) == 0)?TRUE:FALSE; } Bool16 founduser = allowRequest; Bool16 authContinue = true; QTSS_SetValue(theRTSPRequest,qtssRTSPReqURLRealm, 0, sAuthRealm.Ptr, ::strlen(sAuthRealm.Ptr)); if (allowRequest) theErr = QTSSModuleUtils::AuthorizeRequest(theRTSPRequest, &allowRequest, &founduser,&authContinue); if (!allowRequest) theErr = QTSSModuleUtils::AuthorizeRequest(theRTSPRequest, &allowRequest, &founduser,&authContinue); return theErr; }
这个函数对功能是对读写权限进行判断,我们现在的更改比较简单,只要用户名密码正确,就拥有所有的读写权限。
好,经过了这些我们在再次运行程序,发现一推流就崩溃,最后定位到QTAccessFile.cpp->AuthorizeRequest->ReadEntireFile函数,这是因为之前在做SDP缓存的时候修改了QTSSModuleUtils::ReadEntireFile的代码,我们在QTSSModuleUtils.cpp中添加如下函数以及在QTSSModuleUtils.h中添加相应的函数声明。
QTSS_Error QTSSModuleUtils::ReadEntireFileEx(char* inPath, StrPtrLen* outData, QTSS_TimeVal inModDate, QTSS_TimeVal* outModDate) { QTSS_Object theFileObject = NULL; QTSS_Error theErr = QTSS_NoErr; outData->Ptr = NULL; outData->Len = 0; do { // Use the QTSS file system API to read the file theErr = QTSS_OpenFileObject(inPath, 0, &theFileObject); if (theErr != QTSS_NoErr) break; UInt32 theParamLen = 0; QTSS_TimeVal* theModDate = NULL; theErr = QTSS_GetValuePtr(theFileObject, qtssFlObjModDate, 0, (void**)&theModDate, &theParamLen); Assert(theParamLen == sizeof(QTSS_TimeVal)); if(theParamLen != sizeof(QTSS_TimeVal)) break; if(outModDate != NULL) *outModDate = (QTSS_TimeVal)*theModDate; if(inModDate != -1) { // If file hasn't been modified since inModDate, don't have to read the file if(*theModDate <= inModDate) break; } theParamLen = 0; UInt64* theLength = NULL; theErr = QTSS_GetValuePtr(theFileObject, qtssFlObjLength, 0, (void**)&theLength, &theParamLen); if (theParamLen != sizeof(UInt64)) break; if (*theLength > kSInt32_Max) break; // Allocate memory for the file data outData->Ptr = NEW char[ (SInt32) (*theLength + 1) ]; outData->Len = (SInt32) *theLength; outData->Ptr[outData->Len] = 0; // Read the data UInt32 recvLen = 0; theErr = QTSS_Read(theFileObject, outData->Ptr, outData->Len, &recvLen); if (theErr != QTSS_NoErr) { outData->Delete(); break; } Assert(outData->Len == recvLen); }while(false); // Close the file if(theFileObject != NULL) { theErr = QTSS_CloseFileObject(theFileObject); } return theErr; }
之后将QTAccessFile.cpp->AuthorizeRequest中使用到的ReadEntireFile都改为使用ReadEntireFileEx函数。
再次进行推流测试,发现推流一直返回401,这是因为推送库还没有RTSP认证的功能,我们先不做推流验证。因此我们将前面的认证开关由if(false)更改为if(fRequest->GetMethod() != qtssDescribeMethod),也就说只要是推流就跳过认证。(这个地方不太完美,但还找到完美的方法。)
再次进行推流测试,发现这次可以推流成功;然后我们进行拉流测试发现程序又崩溃了,定位到QTSSAccessLogModule.cpp的LogRequest函数中,在此我们将
UInt32 clientBytesRecv = (UInt32)((*rtpBytesSent * (100.0 - *packetLossPercent)) / 100.0);
更改为
UInt32 clientBytesRecv = 0; if(packetLossPercent != NULL) clientBytesRecv = (UInt32)((*rtpBytesSent * (100.0 - *packetLossPercent)) / 100.0);
将if (*rtpPacketsSent == 0)更改为if(rtpPacketsSent == NULL || *rtpPacketsSent == 0)
之后再进行测试,发现不会崩溃了,但是一直提示进行RTSP认证,即使我们输入了上面设置的正确用户名和密码还是不行。原来在进行基本认证时不同平台的处理是不一样的,我们通过将如下的代码
#ifdef __Win32__ // The password is md5 encoded for win32 char md5EncodeResult[120]; // no memory is allocated in this function call MD5Encode(reqPasswdStr, userPasswdStr, md5EncodeResult,sizeof(md5EncodeResult)); if (::strcmp(userPasswdStr, md5EncodeResult) != 0) authenticated = false; #else if (::strcmp(userPasswdStr, (char*)crypt(reqPasswdStr,userPasswdStr)) != 0) authenticated = false; #endif 更改为 #if 0 // The password is md5 encoded for win32 char md5EncodeResult[120]; // no memory is allocated in this function call MD5Encode(reqPasswdStr, userPasswdStr, md5EncodeResult,sizeof(md5EncodeResult)); if (::strcmp(userPasswdStr, md5EncodeResult) != 0) authenticated = false; #else if (::strcmp(userPasswdStr, (char*)crypt(reqPasswdStr,userPasswdStr)) != 0) authenticated = false; #endif
我们发现windows下基本验证的时候对密码又进行了md5运算,这不是我们想要的,我们把它改为一致。
再次进行测试,发现还是不行……经排查,发现是对RTSP请求的清理没有到位,将RTSPSession::CleanupRequest由下面代码
void RTSPSession::CleanupRequest() { if (fRTPSession != NULL) { // Release the ref. OSRefTable* theMap = QTSServerInterface::GetServer()->GetRTPSessionMap(); theMap->Release(fRTPSession->GetRef()); // NULL out any references to this RTP session fRTPSession = NULL; fRoleParams.rtspRequestParams.inClientSession = NULL; } if (this->IsLiveSession() == false) //clear out the ID so it can't be re-used. { fLastRTPSessionID[0] = 0; fLastRTPSessionIDPtr.Set(fLastRTPSessionID, 0); } if (fRequest != NULL) { // Check to see if a filter module has replaced the request. If so, delete // their request now. if (fRequest->GetValue(qtssRTSPReqFullRequest) && fInputStream.GetRequestBuffer()) { if (fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr != fInputStream.GetRequestBuffer()->Ptr) delete[] fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr; } // NULL out any references to the current request //delete fRequest; //fRequest = NULL; fRoleParams.rtspRequestParams.inRTSPRequest = NULL; fRoleParams.rtspRequestParams.inRTSPHeaders = NULL; } fSessionMutex.Unlock(); fReadMutex.Unlock(); // Clear out our last value for request body length before moving onto the next request this->SetRequestBodyLength(-1); }
更改为下面代码
void RTSPSession::CleanupRequest() { if (fRTPSession != NULL) { // Release the ref. OSRefTable* theMap = QTSServerInterface::GetServer()->GetRTPSessionMap(); theMap->Release(fRTPSession->GetRef()); // NULL out any references to this RTP session fRTPSession = NULL; fRoleParams.rtspRequestParams.inClientSession = NULL; } if (this->IsLiveSession() == false) //clear out the ID so it can't be re-used. { fLastRTPSessionID[0] = 0; fLastRTPSessionIDPtr.Set(fLastRTPSessionID, 0); } if (fRequest != NULL) { // Check to see if a filter module has replaced the request. If so, delete // their request now. if (fRequest->GetValue(qtssRTSPReqFullRequest) && fInputStream.GetRequestBuffer()) { if (fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr != fInputStream.GetRequestBuffer()->Ptr) delete[] fRequest->GetValue(qtssRTSPReqFullRequest)->Ptr; } // NULL out any references to the current request delete fRequest; fRequest = NULL; fRoleParams.rtspRequestParams.inRTSPRequest = NULL; fRoleParams.rtspRequestParams.inRTSPHeaders = NULL; } fSessionMutex.Unlock(); fReadMutex.Unlock(); // Clear out our last value for request body length before moving onto the next request this->SetRequestBodyLength(-1); }
再次测试,OK!
如有错误,欢迎指正!
获取更多信息
邮件:support@easydarwin.orgWEB:www.EasyDarwin.org
Copyright © EasyDarwin.org 2012-2016
相关文章推荐
- EasyDarwin开源流媒体服务器支持basic基本认证和digest摘要自定义认证
- EasyDarwin开源流媒体服务器支持basic基本认证和digest摘要认证解析
- EasyDarwin开源流媒体服务器支持basic基本认证和digest摘要认证解析
- 开源流媒体服务器EasyDarwin支持epoll网络模型,大大提升流媒体服务器网络并发性能
- 开源流媒体服务器EasyDarwin支持epoll网络模型,大大提升流媒体服务器网络并发性能
- 开源流媒体服务器EasyDarwin支持epoll网络模型,大大提升流媒体服务器网络并发性能
- EasyDarwin开源流媒体服务器如何实现按需推送直播的
- EasyDarwin开源流媒体服务器提供的RTMP直播推送库
- 开源流媒体服务器--EasyDarwin
- EasyDarwin开源流媒体服务器Work-stealing优化方案
- [转载]EasyDarwin开源流媒体服务器内存管理优化
- EasyPusher安卓直播推流到EasyDarwin开源流媒体服务器工程简析
- EasyPusher安卓直播推流到EasyDarwin开源流媒体服务器工程简析
- 解决用EasyDarwin开源流媒体服务器做HLS直播时Flash Player卡住的问题
- EasyDarwin开源流媒体服务器高性能设计之无锁队列
- EasyDarwin拉流支持基本认证和摘要认证
- Spring Http Basic(基本)和Digest(摘要)验证
- 如何让Selenium支持http basic和Digest HTTP认证
- 解决用EasyDarwin开源流媒体服务器做HLS直播时Flash Player卡住的问题
- 2015年度新增开源软件排名TOP 100,EasyDarwin开源流媒体服务器排名第17