今天完成了GA身份验证模块的实现,具体的功能描述:由于GA面向的是平台用户,只有在我们平台注册了的用户才可以启动我们的应用程序,因此在GA的服务端必须加上用户身份的认证,这就是认证模块开发的初衷。在我们的环境中采用的是Windows AD(活动目录)服务器,我利用的开发库是Novell公司提供的openldap的库,相关的下载地址我会在下面给出,通过它完全可以实现对Windows AD的组织单元中用户实体的增、删、改、查。
参考链接:http://www.novell.com/documentation/developer/samplecode/cldap_sample/ ,这里面用详细的例子实现了如果操作ldap服务器,用它完全可以满足你的需求。
下面我将分三部分说明GA中认证模块是如何实现的:
1> 用户名username和密码password是如何传递的;
2> GA服务端是如何实现用户认证的;
3> GA服务端如何将认证结果返回给客户端的;
第一部分:username和password是如何传递的
在前面的GA实现并发请求的时候我们已经将了特定的游戏名称是如何发送到服务端的,这里的实现方式与它相同,同样是拼接在客户端的启动参数中,它的最终形式例如:rtsp://10.3.1.10:8554/LocalDeformablePRT/houqd_forh30/123456,其中红色标出的部分一次为:游戏名gamename , 用户名username,密码password。只要将需要的参数拼接在上面,在live555实现的RTSP的客户端执行:rtspClient->sendOptionsCommand(continueAfterOPTIONS);后,它就会把在RTSP的OPTIONS阶段将整个URL发送到GA的服务器端,而我们需要做的就是在服务器端将该参数解析出来,很简单吧,下面是服务器端的解析代码:
// added by houqd , parse specifical string , like "LocalDeformablePRT/houqd_forh3/123456" /* param: [In] value : the addr of specifical string [In] sign : the separator in string [Out] c : the count of string separated by sign return: Array of separated by sign */ char** parseValue(char** value , const char sign , int *c) { char *val = *value ; char s = sign ; char **result = NULL ; int count = 0 ; int i = 0 ; int j = 0 ; while((*val)!=' ' && (*val)!='\0') { printf("%c" , *val); if((*val) == sign){ count++; } val++ ; } *c = count ; result = (char **)malloc(sizeof(char *)*(count+1)); if(result == NULL){ fprintf(stderr , "[houqd] malloc execute error.\n"); }else{ fprintf(stderr , "[houqd] malloc execute successed.\n"); } for(int k = 0 ; k <= count ; k++){ result[k] = (char *)malloc(sizeof(char)*1024); } val = *value ; while( (*val)!=' ' && (*val)!='\0' ){ if((*val) != s){ result[i][j++] = *val ; }else{ result[i][j] = '\0'; j = 0 ; i++; } val++ ; } result[i][j] = '\0'; return result ; } // 调用的代码 // added by houqd 2014/02/24 , get username and passwd strcpy(value, strstr(url, "8554")+5); valueCopy = value ; parseValueRes = parseValue(&valueCopy , '/' , &parseValueCount); if(parseValueRes == NULL){ fprintf(stderr , "houqd .. parse username and passwd error.\n"); }else{ appname = parseValueRes[0]; // 游戏名称 username = parseValueRes[1]; // 用户名 passwd = parseValueRes[2]; // 密码 }
第二部分:GA服务端实现用户身份认证
这里需要提到两点,一是:GA服务端认证部分代码结构的实现;二是:C操作ldap身份认证的实现。
1)为了和GA服务端原有代码结构保持一致,单独新建了子项目adldap来为用户身份认证提供接口,adldap子项目创建的是DLL工程,我们最终使用它的dll和lib文件,采用隐式调用(不采用LoadLibrary()的方式load dll文件)的方式来使用他们。最终的项目结构关系为:
2)用户身份认证部分,在GA的server端要实现的只是用户身份的认证,而不需要增加删除和修改的功能。因为添加用户、删除用户以及修改用户信息,在平台的Web端都实现了。由于在Windows AD schema设计的原因,在用户实体的属性中我们无法获得用户的密码信息,而只能得到用户名,那这样如何实现密码的验证呢?通过在Stack Overflow的搜索得知验证用户密码的方式:先查找用户名,如果用户名存在的情况下执行ldap_simple_bind_s(),利用给定的用户名和密码进行二次连接,如果此时能够连上则说明用户身份认证通过,用户名和密码都正确,如果无法连接则说明密码错误;如果连用户名都无法查到,则说明该用户并不存在。
集成在GA中实现用户身份认证的代码(红色标出的为关键函数名):
ADLDAP_API int adldap_authenticate_user(char *username , char *passwd) { LDAP *ld = NULL , *ld_s = NULL ; char *hostname = "adserver.fdaf.some.com"; // ldap服务器的名称,这里是Windows AD服务器的名称 char *baseSearch = "OU=users,DC=fdaf,DC=some,DC=com"; // OU代表的是Windows AD中保存用户信息的组织单元users char *adminUser = "root"; // 拥有管理员权限的用户名 char *adminPasswd = "123456789"; // 密码 char cnFilter[256] ; char **cnValueSearched = NULL ; char usernameSearched[256] ; LDAPMessage *searchResult = NULL ; LDAPMessage *eachEntry = NULL ; int version = LDAP_VERSION3 ; int countSearched = 0 ; int rc = 0 ; memset(cnFilter , 0 , sizeof(cnFilter)); memset(usernameSearched , 0 , sizeof(usernameSearched)); // 1> set adldap version ldap_set_option(NULL , LDAP_OPT_PROTOCOL_VERSION , &version); // 2> initialize the session if( (ld = ldap_init(hostname , LDAP_PORT)) == NULL ){ return -1 ; } // 3> connect to adldap server if( (rc = ldap_simple_bind_s( ld, adminUser, adminPasswd )) != LDAP_SUCCESS){ ldap_unbind(ld); return -1 ; } // 4> search entry by given username strcpy(cnFilter , "cn="); strcat(cnFilter , username); rc = ldap_search_ext_s(ld , baseSearch , LDAP_SCOPE_SUBTREE , cnFilter , NULL , 0 , NULL , NULL , NULL , 0 , &searchResult); if( rc != LDAP_SUCCESS ){ ldap_unbind(ld); return -1 ; } countSearched = ldap_count_messages(ld , searchResult); if(countSearched < 2){ ldap_unbind(ld); return 1317 ; }else if(countSearched > 2){ ldap_unbind(ld); return -1; }else{ for( eachEntry = ldap_first_entry(ld , searchResult) ; eachEntry != NULL ; eachEntry = ldap_next_entry(ld , searchResult) ){ if((cnValueSearched = ldap_get_values(ld , eachEntry , "cn")) != NULL){ strcpy(usernameSearched , *cnValueSearched); } } if(strcmp(usernameSearched , username)){ ldap_unbind(ld); return 1317 ; } } // 5> verify the given password either is corrent , the perfect way is bind to the server use the given username and passwd if((ld_s = ldap_init(hostname , LDAP_PORT)) == NULL){ return -1 ; } if( (rc = ldap_simple_bind_s(ld_s , username , passwd)) != LDAP_SUCCESS ){ ldap_unbind(ld); return 1326 ; } return 0 ; }自己做测试时写的测试代码,实现了增、删、改、查功能:
#include "stdafx.h" #include "ldap.h" //#define ADD_USER 1 // 添加用户成功,注意:此时仅仅是添进去,还没有设置UNIX Attribute,即挂载Linux存储上的用户工作室目录 // 还有此时利用插入进去的帐号还无法登录,和其它帐号相比该帐号左下角有一个灰色的标识 //#define SELECT_USER 1 // 检索用户成功 //#define DEL_USER 1 // 删除用户成功 //#define MOD_USER 1 // 修改用户信息成功(以修改用户在Windows AD上的邮箱作为测试) //#define ADD_TO_CONTAIN_TEST 1 // 以添加到在92上我自己新建的组织单位:houqd为测试基准 #define VERIFY_PASS 1 int main(int argc, _TCHAR* argv[]) { LDAP* ld = NULL ; char* hostname = "vsdevpdc.vsdevel.corp.landhightech.com"; char* baseSearch = "OU=vsusers,DC=vsdevel,DC=corp,DC=landhightech,DC=com"; char* loginUser = "houqd_1111"; char* loginPass = "123456"; LDAPMessage* searchResult = NULL ; LDAPMessage* entry = NULL ; BerElement* ber = NULL ; char* dn = NULL ; // 域名 char* attribute = NULL ; // 存放各种属性 char** values ; // ==================For add entry================= LDAPMod *mods[5] ; LDAPMod modObjectClass , modCn , modMailA , modUserPassword; INT modsNum = 5 ; char* objectclassValues[5] ; char* commonnameValues[2] ; char* mailValues[2] ; char* passValues[2] ; char* vclass[5] ; char* ouname = "OU=vsusers,DC=vsdevel,DC=corp,DC=landhightech,DC=com"; //char* ouname = "ou=vsusers,dc=vsochina,dc=com"; char* newdn = NULL ; // ======================END======================= // ==================For delete entry======================= char* deldn = "CN=houqd_forh1,OU=vsusers,DC=vsdevel,DC=corp,DC=landhightech,DC=com"; // ========================END========================= // ==================For change entry=============== LDAPMod modMail ; char* modMailValues[2]; LDAPMod* modify[2]; char* modifydn = "CN=houqd_forh16,OU=vsusers,DC=vsdevel,DC=corp,DC=landhightech,DC=com"; // =======================END======================= int version = LDAP_VERSION3 ; int rc = 0; // 1> 设置LDAP选项 ldap_set_option( NULL, LDAP_OPT_PROTOCOL_VERSION, &version); // 2> 初始化session ld = ldap_init(hostname , LDAP_PORT); if( ld == NULL ){ printf("Execute ldap_init() error.\n"); system("pause"); exit(1); }else{ printf("Execute ldap_init() success.\n"); } // 3> 连接到LDAP服务器 rc = ldap_simple_bind_s( ld, loginUser, loginPass ); if( rc != LDAP_SUCCESS ){ printf("Execute ldap_simple_bind_s() error.\n"); ldap_unbind_s(ld); system("pause"); exit(1); }else{ printf("Execute ldap_simple_bind_s() success .\n"); } #ifdef VERIFY_PASS LDAPMessage *result ; LDAPMessage *h_entry ; rc = ldap_search_ext_s(ld , baseSearch , LDAP_SCOPE_SUBTREE , "cn=houqd_1111" , NULL , 0 , NULL , NULL , NULL , 0 , &result); int h_count = ldap_count_messages(ld , result); char** h_values ; if(rc != LDAP_SUCCESS){ printf("Execute ldap_search_ext_s() falied.\n"); system("pause"); exit(1); } printf("-----:%d.\n" , h_count); for( h_entry = ldap_first_entry(ld , result) ; h_entry != NULL ; h_entry = ldap_next_entry(ld , result)){ if((h_values = ldap_get_values(ld , h_entry , "cn")) != NULL){ printf("--------::%s.\n" , *h_values); } } #endif #ifdef ADD_TO_CONTAIN_TEST // 3.0.5> 添加entry到container中测试 char* con_dn = "OU=houqd,DC=vsdevel,DC=corp,DC=landhightech,DC=com"; LDAPMod* con_mods[5]; LDAPMod modObjectClass_container , modCn_container , modSn_container , modGivenName_container; char *objectClassValues[5] , *cnValues[2] , *snValues[2] , *givenNameValues[2]; char *con_cn ; modObjectClass_container.mod_op = LDAP_MOD_ADD; modObjectClass_container.mod_type = "objectClass"; objectClassValues[0] = "top"; objectClassValues[1] = "person"; objectClassValues[2] = "organizationalPerson"; objectClassValues[3] = "user"; objectClassValues[4] = NULL; modObjectClass_container.mod_values = objectClassValues ; modCn_container.mod_op = LDAP_MOD_ADD; modCn_container.mod_type = "cn"; cnValues[0] = "houqd_forh17"; cnValues[1] = NULL ; modCn_container.mod_values = cnValues ; modSn_container.mod_op = LDAP_MOD_ADD; modSn_container.mod_type = "sn"; snValues[0] = "houqd_forh17"; snValues[1] = NULL ; modSn_container.mod_values = snValues ; modGivenName_container.mod_op = LDAP_MOD_ADD ; modGivenName_container.mod_type = "givenName"; givenNameValues[0] = "houqd_forh17"; givenNameValues[1] = NULL ; modGivenName_container.mod_values = givenNameValues ; con_mods[0] = &modObjectClass_container; con_mods[1] = &modCn_container; con_mods[2] = &modSn_container; con_mods[3] = &modGivenName_container; con_mods[4] = NULL; con_cn = (char *)malloc(strlen("cn=houqd_forh17,")+strlen(con_dn)+1); strcpy(con_cn , "cn=houqd_forh17,"); strcat(con_cn , con_dn); printf("[%s][now rc = %d]\n" , con_cn , rc); rc = ldap_add_ext_s(ld , con_cn , con_mods , NULL , NULL); // rc == 16 代表在mode[k]->mode_type中指定的属性不存在。 // rc == 34 if( rc != LDAP_SUCCESS ){ printf("Execute ldap_add_ext_s() error.\n"); ldap_unbind_s(ld); // here we must to free the pointer . system("pause"); exit(1); } // ----------end #endif // 3.1> 添加用户到LDAP SERVER中, 也就是向里面添加一个实体:Entry #ifdef ADD_USER modObjectClass.mod_op = LDAP_MOD_ADD; modObjectClass.mod_type = "objectClass"; objectclassValues[0] = "top"; objectclassValues[1] = "person"; objectclassValues[2] = "organizationalPerson"; objectclassValues[3] = "user"; objectclassValues[4] = NULL ; modObjectClass.mod_values = objectclassValues; modCn.mod_op = LDAP_MOD_ADD ; modCn.mod_type = "cn"; commonnameValues[0] = "houqd_forh19"; // 一定要注意,此时cn的值一定要和下面newdn中cn=...的值保持一致,不然无法添加进去,一直报错。 commonnameValues[1] = NULL ; modCn.mod_values = commonnameValues ; modMailA.mod_op = LDAP_MOD_ADD ; modMailA.mod_type = "mail"; mailValues[0] = "[email protected]"; mailValues[1] = NULL ; modMailA.mod_values = mailValues ; modUserPassword.mod_op = LDAP_MOD_ADD ; modUserPassword.mod_type = "userPassword"; passValues[0] = "dell_456"; passValues[1] = NULL ; modUserPassword.mod_values = passValues ; mods[0] = &modObjectClass ; mods[1] = &modCn ; mods[2] = &modMailA ; mods[3] = &modUserPassword ; mods[4] = NULL ; newdn = (char *)malloc(strlen("cn=houqd_forh19,")+strlen(ouname)+1); // 注意:此处cn的值一定要和上面的cn值保持一直,不然添加用户会无法成功,会报错。 strcpy(newdn , "cn=houqd_forh19,"); strcat(newdn , ouname); printf("[%s]\n" , newdn); rc = ldap_add_ext_s(ld , newdn , mods , NULL , NULL); // rc == 16 代表在mode[k]->mode_type中指定的属性不存在。 // rc == 34 if( rc != LDAP_SUCCESS ){ printf("Execute ldap_add_ext_s() error.\n"); ldap_unbind_s(ld); // here we must to free the pointer . system("pause"); exit(1); } #endif #ifdef SELECT_USER // 4> 查找指定用户,例如:houqd_forh16 rc = ldap_search_ext_s( ld , baseSearch , LDAP_SCOPE_ONELEVEL , "(cn=houqd_1111)" , NULL , 0 , NULL , NULL , NULL , LDAP_NO_LIMIT ,&searchResult); if(rc != LDAP_SUCCESS){ printf("Execute ldap_search_ext_s() error.\n"); ldap_msgfree(searchResult); ldap_unbind_s(ld); system("pause"); exit(1); } printf("New Entry added successed. \n"); int count = ldap_count_messages(ld , searchResult); printf("The search result count = %d.\n" , count); // 5> 遍历结果 int i = 1 ; for(entry = ldap_first_entry(ld , searchResult) ; entry != NULL ; entry = ldap_next_entry(ld , searchResult)){ // 1. dn : 域名 if( (dn = ldap_get_dn(ld , entry)) != NULL ){ printf("(Identifie : %d) DN = %s.\n" , i ,dn); ldap_memfree(dn); } // 2. 获取各种属性 for(attribute = ldap_first_attribute(ld , entry , &ber) ; attribute != NULL ; attribute = ldap_next_attribute(ld , entry , ber)){ if((values = ldap_get_values(ld , entry , attribute)) != NULL ){ for(int j = 0 ; values[j] != NULL ; j++){ printf(" %s:%s\n" , attribute , values[j]); } ldap_memfree(attribute); } } i++ ; ber_free(ber , 0); } #endif #ifdef DEL_USER printf("deldn = %s.\n" , deldn); rc = ldap_delete_ext_s(ld , deldn , NULL , NULL); if( rc != LDAP_SUCCESS){ printf("Execute ldap_delete_ext_s() error.\n"); ldap_unbind_s(ld); system("pause"); exit(1); }else{ printf("Execute ldap_delete_ext_s() successed .\n"); } #endif #ifdef MOD_USER modMail.mod_op = LDAP_MOD_REPLACE ; modMail.mod_type = "mail"; modMailValues[0] = "[email protected]"; modMailValues[1] = NULL ; modMail.mod_values = modMailValues ; modify[0] = &modMail ; modify[1] = NULL ; rc = ldap_modify_ext_s(ld , modifydn , modify , NULL , NULL); if( rc != LDAP_SUCCESS ){ printf("Execute ldap_modify_ext_s() failed. \n"); ldap_unbind_s(ld); system("pause"); exit(1); }else{ printf("Execute ldap_modify_ext_s() successed. \n"); } #endif ldap_msgfree(searchResult); ldap_unbind_s(ld); system("pause"); return 0; }
第三部分:GA服务端如何将认证结果返回给客户端
为了探讨方便,这里将RTSP协议通信图解放上来:
服务器端解析客户端传来的参数的位置发生在:客户端发送了OPTIONS request,服务端还没有发送OPTIONS reply的这个过程中;我们的具体实现是在这个过程中进行用户身份的验证,如果用户身份认证通过,则不做任何操作,继续下面的启动游戏以及发送OPTIONS reply;如果用户验证发送错误,会获得一个错误码,不同的错误码代表不通的含义,比如:用户名不存在、密码错误等。而发生错误时我们要做的是同样模拟OPTIONS reply的过程,发送200 OK的状态码等,但是需要将错误信息加在"Public: ErrorCode:1326\r\n"这里面,这样发送回客户端,客户端在收到后会进入:rtspClient->sendOptionsCommand(continueAfterOPTIONS); continueAfterOPTIONS的处理函数里面。
服务器端的代码:
// authentication username and passwd int auth_code = adldap_authenticate_user(username , passwd); if( auth_code != 0 ){ // authenticate failed , send the error code to client , and pause to continue execute // 硬编码reply的OPTIONS的各个信息,将错误码加载Public的信息中,并send出去,模拟正常OPTIONS的响应 char *echostr1 = "RTSP/1.0 200 OK\r\n"; char *echostr2 = "CSeq: 2\r\n"; char echostr3[100]; char *echostr4 = "\r\n"; sprintf(echostr3 , "Public: ErrorCode:%d\r\n" , auth_code); send(cs , echostr1 , strlen(echostr1) , 0); send(cs , echostr2 , strlen(echostr2) , 0); send(cs , echostr3 , strlen(echostr3) , 0); send(cs , echostr4 , strlen(echostr4) , 0); if(cs!=0) close(cs); return NULL ; }
客户端的代码:
void continueAfterOPTIONS(RTSPClient* rtspClient, int resultCode, char* resultString) { rtsperror("I am in continueAfterOPTIONS Connect to %s.\n", rtspClient->url()); rtsperror("[houqd...] resultString = %s.\n" , resultString); // added by houqd 2014/02/25 , analyze resultString judge if exists errorcode // 解析resultString字符串,如果是正常OPTIONS的返回,则resultString = "Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY"; // 而如果发生错误,则类似:resultString = "Public: ErrorCode:1317"; // 1317:用户不存在 // 1326:密码错误 // 其它:GA server端,adldap函数执行失败 char *pos = NULL ; char errCode_s[10] ; int errCode_i = 0 ; pos = strstr(resultString , "ErrorCode"); if( pos != NULL ){ strcpy(errCode_s , pos+10); errCode_i = atoi(errCode_s); switch(errCode_i){ case 1317: rtsperror("[error] Username doesn't exists.\n"); break ; case 1326: rtsperror("[error] Password incorrect.\n"); break ; default: rtsperror("[error] GA server adldap original function execute error.\n "); break; } shutdownStream(rtspClient); }else{ rtspClient->sendDescribeCommand(continueAfterDESCRIBE); } // An unrecoverable error occurred with this stream. //shutdownStream(rtspClient); }
以上就是GA用户认证模块的全部实现,关键地方是:C操作ldap以及合理地利用RTSP通信的各个阶段。下一阶段面临的工作是:用户存储的挂载,以及如何对用户访问磁盘文件做权限控制,关于这个至今还没有明确的实现思路,愁啊!!
只有今天的我比昨天的我有一些的进步,就是对自己最大的鼓励,加油!!!
C操作ldap服务器的开发库下载路径:http://download.csdn.net/detail/houqingdong2012/6961375