mirror of
https://github.com/ZLMediaKit/ZLMediaKit.git
synced 2024-11-22 19:00:01 +08:00
http文件鉴权支持自定义错误提示
This commit is contained in:
parent
cfbdda0698
commit
c7cc082d95
@ -673,34 +673,23 @@ void installWebApi() {
|
||||
};
|
||||
|
||||
API_REGIST(hook,on_http_access,{
|
||||
#if 0
|
||||
//能访问根目录以及根目录下所有文件10分钟
|
||||
val["path"] = "/";
|
||||
val["second"] = 10 * 60;
|
||||
#else
|
||||
//在这里根据allArgs["params"](url参数)来判断该http客户端是否有权限访问该文件
|
||||
if(!checkAccess(allArgs["params"])){
|
||||
//无访问权限
|
||||
val["err"] = "无访问权限";
|
||||
//仅限制访问当前目录
|
||||
val["path"] = "";
|
||||
//标记该客户端无权限1分钟,1分钟之内它凭此cookie访问将都无权限
|
||||
//如果客户端不支持cookie,那么可以根据url参数来追踪用户,请参考kBroadcastTrackHttpClient事件
|
||||
//如果服务器未处理kBroadcastTrackHttpClient事件,那么ZLMediaKit会根据ip和端口追踪用户
|
||||
//标记该客户端无权限1分钟
|
||||
val["second"] = 60;
|
||||
return;
|
||||
}
|
||||
|
||||
//只能访问本文件,且只授权10分钟,访问其他文件都要另外授权
|
||||
if(allArgs["is_dir"].as<bool>()){
|
||||
//访问的是目录,该授权cookie只对该目录有效
|
||||
val["path"] = (string)allArgs["path"];
|
||||
}else{
|
||||
//访问的是文件,那么我们授予客户端访问所在目录的权限
|
||||
string dir = allArgs["path"].substr(0,allArgs["path"].rfind("/") + 1);
|
||||
val["path"] = dir;
|
||||
}
|
||||
//该http客户端用户被授予10分钟的访问权限,该权限仅限访问特定目录
|
||||
//可以访问
|
||||
val["err"] = "";
|
||||
//只能访问当前目录
|
||||
val["path"] = "";
|
||||
//该http客户端用户被授予10分钟的访问权限,该权限仅限访问当前目录
|
||||
val["second"] = 10 * 60;
|
||||
#endif
|
||||
});
|
||||
|
||||
|
||||
|
@ -399,13 +399,13 @@ void installWebHook(){
|
||||
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){
|
||||
if(sender.get_peer_ip() == "127.0.0.1" && args._param_strs == hook_adminparams){
|
||||
//如果是本机或超级管理员访问,那么不做访问鉴权;权限有效期1个小时
|
||||
invoker("/",60 * 60);
|
||||
invoker("","",60 * 60);
|
||||
return;
|
||||
}
|
||||
if(!hook_enable || hook_http_access.empty()){
|
||||
//未开启http文件访问鉴权,那么允许访问,但是每次访问都要鉴权;
|
||||
//因为后续随时都可能开启鉴权(重载配置文件后可能重新开启鉴权)
|
||||
invoker("/",0);
|
||||
invoker("","",0);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -423,15 +423,13 @@ void installWebHook(){
|
||||
do_http_hook(hook_http_access,body, [invoker](const Value &obj,const string &err){
|
||||
if(!err.empty()){
|
||||
//如果接口访问失败,那么仅限本次没有访问http服务器的权限
|
||||
invoker("",0);
|
||||
invoker(err,"",0);
|
||||
return;
|
||||
}
|
||||
//path参数是该客户端能访问的顶端目录,该目录下的所有文件它都能访问
|
||||
//second参数规定该cookie超时时间,超过这个时间后,用户需要重新鉴权
|
||||
//如果path为空字符串,则为禁止访问任何目录
|
||||
//如果second为0,本次鉴权结果不缓存
|
||||
//如果被禁止访问文件,在cookie有效期内,假定再次访问的url参数变了,那么也能立即触发重新鉴权操作
|
||||
invoker(obj["path"].asString(),obj["second"].asInt());
|
||||
//err参数代表不能访问的原因,空则代表可以访问
|
||||
//path参数是该客户端能访问或被禁止的顶端目录,如果path为空字符串,则表述为当前目录
|
||||
//second参数规定该cookie超时时间,如果second为0,本次鉴权结果不缓存
|
||||
invoker(obj["err"].asString(),obj["path"].asString(),obj["second"].asInt());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -76,6 +76,10 @@ bool HttpServerCookie::isExpired() {
|
||||
return _ticker.elapsedTime() > _max_elapsed * 1000;
|
||||
}
|
||||
|
||||
std::shared_ptr<lock_guard<mutex> > HttpServerCookie::getLock(){
|
||||
return std::make_shared<lock_guard<mutex> >(_mtx);
|
||||
}
|
||||
|
||||
string HttpServerCookie::cookieExpireTime() const{
|
||||
char buf[64];
|
||||
time_t tt = time(NULL) + _max_elapsed;
|
||||
@ -105,7 +109,7 @@ void HttpCookieManager::onManager() {
|
||||
for (auto it_cookie = it_name->second.begin() ; it_cookie != it_name->second.end() ; ){
|
||||
if(it_cookie->second->isExpired()){
|
||||
//cookie过期,移除记录
|
||||
WarnL << it_cookie->second->getUid() << " cookie过期";
|
||||
DebugL << it_cookie->second->getUid() << " cookie过期:" << it_cookie->second->getCookie();
|
||||
it_cookie = it_name->second.erase(it_cookie);
|
||||
continue;
|
||||
}
|
||||
@ -114,7 +118,7 @@ void HttpCookieManager::onManager() {
|
||||
|
||||
if(it_name->second.empty()){
|
||||
//该类型下没有任何cooki记录,移除之
|
||||
WarnL << "该path下没有任何cooki记录:" << it_name->first;
|
||||
DebugL << "该path下没有任何cooki记录:" << it_name->first;
|
||||
it_name = _map_cookie.erase(it_name);
|
||||
continue;
|
||||
}
|
||||
@ -152,6 +156,7 @@ HttpServerCookie::Ptr HttpCookieManager::getCookie(const string &cookie_name,con
|
||||
}
|
||||
if(it_cookie->second->isExpired()){
|
||||
//cookie过期
|
||||
DebugL << "cookie过期:" << it_cookie->second->getCookie();
|
||||
it_name->second.erase(it_cookie);
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -102,6 +102,12 @@ public:
|
||||
* @return
|
||||
*/
|
||||
bool isExpired();
|
||||
|
||||
/**
|
||||
* 获取区域锁
|
||||
* @return
|
||||
*/
|
||||
std::shared_ptr<lock_guard<mutex> > getLock();
|
||||
private:
|
||||
string cookieExpireTime() const ;
|
||||
private:
|
||||
@ -110,6 +116,7 @@ private:
|
||||
string _cookie_uuid;
|
||||
uint64_t _max_elapsed;
|
||||
Ticker _ticker;
|
||||
mutex _mtx;
|
||||
std::weak_ptr<HttpCookieManager> _manager;
|
||||
};
|
||||
|
||||
|
@ -49,11 +49,10 @@ using namespace toolkit;
|
||||
namespace mediakit {
|
||||
|
||||
static int kSockFlags = SOCKET_DEFAULE_FLAGS | FLAG_MORE;
|
||||
static int kHlsCookieSecond = 10 * 60;
|
||||
static const string kCookieName = "ZL_COOKIE";
|
||||
static const string kAccessPathKey = "kAccessPathKey";
|
||||
static const string kAccessDirUnauthorized = "你没有权限访问该目录";
|
||||
static const string kAccessFileUnauthorized = "你没有权限访问该文件";
|
||||
|
||||
static const string kCookiePathKey = "kCookiePathKey";
|
||||
static const string kAccessErrKey = "kAccessErrKey";
|
||||
|
||||
string dateStr() {
|
||||
char buf[64];
|
||||
@ -346,13 +345,8 @@ static inline bool checkHls(BroadcastHttpAccessArgs){
|
||||
}
|
||||
//访问的hls.m3u8结尾,我们转换成kBroadcastMediaPlayed事件
|
||||
Broadcast::AuthInvoker mediaAuthInvoker = [invoker,path](const string &err){
|
||||
if(err.empty() ){
|
||||
//鉴权通过,允许播放一个小时
|
||||
invoker(path.substr(0,path.rfind("/") + 1),60 * 60);
|
||||
}else{
|
||||
//鉴权失败,10秒内不允许播放hls
|
||||
invoker("",10);
|
||||
}
|
||||
//cookie有效期为kHlsCookieSecond
|
||||
invoker(err,"",kHlsCookieSecond);
|
||||
};
|
||||
|
||||
auto args_copy = args;
|
||||
@ -360,13 +354,13 @@ static inline bool checkHls(BroadcastHttpAccessArgs){
|
||||
return NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPlayed,args_copy,mediaAuthInvoker,sender);
|
||||
}
|
||||
|
||||
inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const function<void(bool canAccess,const HttpServerCookie::Ptr &cookie)> &callback_in){
|
||||
inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const function<void(const string &errMsg,const HttpServerCookie::Ptr &cookie)> &callback_in){
|
||||
auto path = path_in;
|
||||
replace(const_cast<string &>(path),"//","/");
|
||||
|
||||
auto callback = [callback_in,this](bool canAccess,const HttpServerCookie::Ptr &cookie){
|
||||
auto callback = [callback_in,this](const string &errMsg,const HttpServerCookie::Ptr &cookie){
|
||||
try {
|
||||
callback_in(canAccess,cookie);
|
||||
callback_in(errMsg,cookie);
|
||||
}catch (SockException &ex){
|
||||
if(ex){
|
||||
shutdown(ex);
|
||||
@ -386,48 +380,63 @@ inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const f
|
||||
}
|
||||
|
||||
if(cookie){
|
||||
//找到了cookie
|
||||
auto accessPath = (*cookie)[kAccessPathKey];
|
||||
if (!accessPath.empty() && path.find(accessPath) == 0) {
|
||||
//用户是有权限访问该目录
|
||||
callback(true, nullptr);
|
||||
//找到了cookie,对cookie上锁先
|
||||
auto lck = cookie->getLock();
|
||||
auto accessErr = (*cookie)[kAccessErrKey];
|
||||
if (accessErr.empty() && path.find((*cookie)[kCookiePathKey]) == 0) {
|
||||
//用户有权限访问该目录
|
||||
callback("", nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
//用户无权限访问,我们看看用户的url参数变了没有
|
||||
//如果url参数变了,那么重新鉴权
|
||||
if (cookie->getUid() == _parser.Params()) {
|
||||
if (_parser.Params().empty() || _parser.Params() == cookie->getUid()) {
|
||||
//url参数未变,那么判断无权限访问
|
||||
callback(false, nullptr);
|
||||
callback(accessErr.empty() ? "无权限访问该目录" : accessErr, nullptr);
|
||||
return;
|
||||
}
|
||||
//如果url参数变了,那么旧cookie失效,我们重新鉴权
|
||||
HttpCookieManager::Instance().delCookie(cookie);
|
||||
}
|
||||
|
||||
//该用户从来未获取过cookie,这个时候我们广播是否允许该用户访问该http目录
|
||||
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
|
||||
HttpAccessPathInvoker accessPathInvoker = [weakSelf, callback, uid , path] (const string &accessPath, int cookieLifeSecond) {
|
||||
HttpAccessPathInvoker accessPathInvoker = [weakSelf,callback,uid,path,is_dir] (const string &errMsg,const string &cookie_path_in, int cookieLifeSecond) {
|
||||
string cookie_path = cookie_path_in;
|
||||
if(cookie_path.empty()){
|
||||
//如果未设置鉴权目录,那么我们采用当前目录
|
||||
if(is_dir){
|
||||
cookie_path = path;
|
||||
}else{
|
||||
cookie_path = path.substr(0,path.rfind("/") + 1);
|
||||
}
|
||||
}
|
||||
|
||||
HttpServerCookie::Ptr cookie ;
|
||||
if(cookieLifeSecond) {
|
||||
//本次鉴权设置了有效期,我们把鉴权结果缓存在cookie中
|
||||
cookie = HttpCookieManager::Instance().addCookie(kCookieName, uid, cookieLifeSecond);
|
||||
//对cookie上锁
|
||||
auto lck = cookie->getLock();
|
||||
//记录用户能访问的路径
|
||||
(*cookie)[kCookiePathKey] = cookie_path;
|
||||
//记录能否访问
|
||||
(*cookie)[kAccessErrKey] = errMsg;
|
||||
}
|
||||
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
//自己已经销毁
|
||||
return;
|
||||
}
|
||||
strongSelf->async([weakSelf, callback, accessPath, cookieLifeSecond, uid , path]() {
|
||||
strongSelf->async([weakSelf,callback,cookie,errMsg]() {
|
||||
//切换到自己线程
|
||||
auto strongSelf = weakSelf.lock();
|
||||
if (!strongSelf) {
|
||||
//自己已经销毁
|
||||
return;
|
||||
}
|
||||
if(cookieLifeSecond){
|
||||
//我们给用户生成追踪cookie
|
||||
auto cookie = HttpCookieManager::Instance().addCookie(kCookieName,uid,cookieLifeSecond);
|
||||
//记录用户能访问的路径
|
||||
(*cookie)[kAccessPathKey] = accessPath;
|
||||
//判断该用户是否有权限访问该目录,并且设置客户端cookie
|
||||
callback(!accessPath.empty() && path.find(accessPath) == 0, cookie);
|
||||
}else{
|
||||
//仅限本次访问文件
|
||||
callback(!accessPath.empty() && path.find(accessPath) == 0, nullptr);
|
||||
}
|
||||
callback(errMsg, cookie);
|
||||
});
|
||||
};
|
||||
|
||||
@ -439,7 +448,7 @@ inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const f
|
||||
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess,_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this);
|
||||
if(!flag){
|
||||
//此事件无人监听,我们默认都有权限访问
|
||||
callback(true, nullptr);
|
||||
callback("", nullptr);
|
||||
}
|
||||
|
||||
}
|
||||
@ -497,15 +506,16 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
||||
}
|
||||
|
||||
//判断是否有权限访问该目录
|
||||
canAccessPath(_parser.Url(),true,[this,bClose,strFile,strMeun](bool canAccess,const HttpServerCookie::Ptr &cookie){
|
||||
if(!canAccess){
|
||||
const_cast<string &>(strMeun) = kAccessDirUnauthorized;
|
||||
auto path = _parser.Url();
|
||||
canAccessPath(_parser.Url(),true,[this,bClose,strFile,strMeun,path](const string &errMsg,const HttpServerCookie::Ptr &cookie){
|
||||
if(!errMsg.empty()){
|
||||
const_cast<string &>(strMeun) = errMsg;
|
||||
}
|
||||
auto headerOut = makeHttpHeader(bClose,strMeun.size());
|
||||
if(cookie){
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kAccessPathKey]);
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey]);
|
||||
}
|
||||
sendResponse(canAccess ? "200 OK" : "401 Unauthorized" , headerOut, strMeun);
|
||||
sendResponse(errMsg.empty() ? "200 OK" : "401 Unauthorized" , headerOut, strMeun);
|
||||
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access folder");
|
||||
});
|
||||
return;
|
||||
@ -534,7 +544,16 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
||||
|
||||
auto parser = _parser;
|
||||
//判断是否有权限访问该文件
|
||||
canAccessPath(_parser.Url(),false,[this,parser,tFileStat,pFilePtr,bClose,strFile](bool canAccess,const HttpServerCookie::Ptr &cookie){
|
||||
canAccessPath(_parser.Url(),false,[this,parser,tFileStat,pFilePtr,bClose,strFile](const string &errMsg,const HttpServerCookie::Ptr &cookie){
|
||||
if(!errMsg.empty()){
|
||||
auto headerOut = makeHttpHeader(bClose,errMsg.size());
|
||||
if(cookie){
|
||||
headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey]);
|
||||
}
|
||||
sendResponse("401 Unauthorized" , headerOut, errMsg);
|
||||
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file failed");
|
||||
}
|
||||
|
||||
//判断是不是分节下载
|
||||
auto &strRange = parser["Range"];
|
||||
int64_t iRangeStart = 0, iRangeEnd = 0;
|
||||
@ -552,8 +571,7 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
||||
pcHttpResult = "206 Partial Content";
|
||||
fseek(pFilePtr.get(), iRangeStart, SEEK_SET);
|
||||
}
|
||||
auto httpHeader = canAccess ? makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strFile.data()))
|
||||
: makeHttpHeader(bClose, kAccessFileUnauthorized.size());
|
||||
auto httpHeader = makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strFile.data()));
|
||||
if (strRange.size() != 0) {
|
||||
//分节下载返回Content-Range头
|
||||
httpHeader.emplace("Content-Range",StrPrinter<<"bytes " << iRangeStart << "-" << iRangeEnd << "/" << tFileStat.st_size<< endl);
|
||||
@ -564,12 +582,10 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
|
||||
httpHeader["Access-Control-Allow-Credentials"] = "true";
|
||||
}
|
||||
|
||||
if(cookie){
|
||||
httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kAccessPathKey]);
|
||||
}
|
||||
//先回复HTTP头部分
|
||||
sendResponse(canAccess ? pcHttpResult : "401 Unauthorized" , httpHeader,canAccess ? "" : kAccessFileUnauthorized);
|
||||
if (!canAccess || iRangeEnd - iRangeStart < 0) {
|
||||
sendResponse(pcHttpResult,httpHeader,"");
|
||||
|
||||
if (iRangeEnd - iRangeStart < 0) {
|
||||
//文件是空的!
|
||||
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file");
|
||||
}
|
||||
|
@ -52,7 +52,12 @@ public:
|
||||
const KeyValue &headerOut,
|
||||
const string &contentOut)> HttpResponseInvoker;
|
||||
|
||||
typedef std::function<void(const string &accessPath, int cookieLifeSecond)> HttpAccessPathInvoker;
|
||||
/**
|
||||
* @param errMsg 如果为空,则代表鉴权通过,否则为错误提示
|
||||
* @param accessPath 运行或禁止访问的根目录
|
||||
* @param cookieLifeSecond 鉴权cookie有效期
|
||||
**/
|
||||
typedef std::function<void(const string &errMsg,const string &accessPath, int cookieLifeSecond)> HttpAccessPathInvoker;
|
||||
|
||||
HttpSession(const Socket::Ptr &pSock);
|
||||
virtual ~HttpSession();
|
||||
@ -125,7 +130,7 @@ private:
|
||||
* @param is_dir path是否为目录
|
||||
* @param callback 有权限或无权限的回调
|
||||
*/
|
||||
inline void canAccessPath(const string &path,bool is_dir,const function<void(bool canAccess,const HttpServerCookie::Ptr &cookie)> &callback);
|
||||
inline void canAccessPath(const string &path,bool is_dir,const function<void(const string &errMsg,const HttpServerCookie::Ptr &cookie)> &callback);
|
||||
|
||||
/**
|
||||
* 获取用户唯一识别id
|
||||
|
Loading…
Reference in New Issue
Block a user