服务器架设笔记——搭建用户注册和验证功能

来源:互联网 时间:1970-01-01


  之前介绍的Apache Httpd相关内容,都是些零散的知识点。而实际运用中,我们要根据不同的业务,将这些知识点连接起来以形成各种组合,来满足我们的需求。(转载请指明出于breaksoftware的csdn博客)

        本文我将以用户注册、登陆和免登等这些业务需求,将之前四篇介绍的知识点串起来,形成一组可用的功能。但是,本例子只是为了完成功能,而不涉及相关优化——比如数据库的访问,我觉得是可以优化的——但是优化不是本文的主题。

        网上有很多Apache+PHP的方案,诚然这个组合可以方便快速的搭建业务性功能,但是我不会写PHP,所以我还是用老掉牙的C去写相关模块。

        用户注册和登陆这个大家一般都明白。但是什么叫免登,可能有些同学还不清楚。举个例子,比如我们登陆某网站后,我们再在其子页面中跳转,往往还是处于登陆状态。但是服务器如何确定这个用户的登陆状态,除了像长连接等方案外,通过协议约定也是一种方案。我们约定:在用户成功注册和登陆后,会访问给客户端请求一个加密字段。用户之后的请求都需要带上这个加密字段,以供服务器验证。

接口定义

    注册

      路径:login

      参数:

  • uid        字符串,用户ID
  • pwd      字符串,密码 
  • did        字符串,设备唯一标志
  • action   字符串,行为。注册时该值为new
      返回:
  • res          整型,0 成功 ,1 用户名已存在, 2 其他失败
  • session  字符串,如果res为0, 则该字段有值,否则为空串

    登陆

      路径:login      参数:
  • uid        字符串,用户ID
  • pwd      字符串,密码 
  • did        字符串,设备唯一标志
      返回:
  • res          整型,0 成功, 其他 失败
  • session  字符串,如果res为0, 则该字段有值,否则为空串

    免登

      路径:任何路径(包括login)

      参数:

  • uid        字符串,用户ID
  • ss          字符串,用于免登的校验标志 
  • did        字符串,设备唯一标志
      返回:根据不同业务,有不同的返回值。免登只是这个请求的一种协助方式。      

模块划分

        根据我们的业务特点,可以拆分出如下4个独立模块(so):
  1. 注册和登陆;
  2. 免登;
  3. 公用库;
  4. json库;这儿需要说明下,我们将使用json作为返回参数的格式。虽然XML是apache apr库中一个可用模块,也是可以用来组织返回数据,但是json还是更加主流。
        

    Json库

        因为apache httpd是C语言写的,所以为了统一风格以及免除之后一切编译相关的问题,我选在了同样的C写的json库——CJson。我们只选用cJSON.h和cJson.c两个文件作为库的原始文件,并编写一个编译脚本

[plain] view plaincopy
  1. gcc cJSON.c -fPIC -lm -shared -o libcjson.so                                                                                      
  2. cp libcjson.so /usr/local/apache2/modules/  

    基础库——utils

        我将基础库分为如下几个功能集:
  1. 加解密;在登陆校验等业务中会使用到。
  2. 编码;base64编码和解码是服务的基础功能。
  3. hash;md5等是必要功能。
  4. 其他辅助函数;一些函数比较复杂,在多个模块中都要被使用到,所以把他们放到基础库中,供各个模块使用。
        编码和Hash没什么好说的,apr库里提供了便捷的方法。加解密在apr-util里也有相应的封装。这儿需要指出的是,我们在编译apr-util时需要指定参数--with-crypto。有的文章上说,还要通过--with-openssl来指定使用openssl库。而我试验发现通过指定该参数,反而会导致加解密模块不可用。因为我们还要使用数据库,所以我们如此编译apr-util[plain] view plaincopy
  1. ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr --with-crypto --with-mysql  
        我们通过查看/usr/local/apr-util/include/apr-1/apu.h文件中相应宏的定义来知晓相应功能是否已被启用[cpp] view plaincopy
  1. #define APU_HAVE_PGSQL         0  
  2. #define APU_HAVE_MYSQL         1  
  3. #define APU_HAVE_SQLITE3       0  
  4. #define APU_HAVE_SQLITE2       0  
  5. #define APU_HAVE_ORACLE        0  
  6. #define APU_HAVE_FREETDS       0  
  7. #define APU_HAVE_ODBC          0  
  8.   
  9. #define APU_HAVE_CRYPTO        1                                                                                                                                            
  10. #define APU_HAVE_OPENSSL       1  
  11. #define APU_HAVE_NSS           0  
        但是非常不幸的是,我参考例子写的一段加解密代码,在测试代码中可以正确运行,而在请求线程中却出现了一些诡异的现象。于是我只能直接使用openssl中的API进行加解密。
        使用如下指令编译openssl,将产出动态链接库,我们将libcrypto.so拷贝到apache httpd的module目录下。[plain] view plaincopy
  1. make clean  
  2. ./config --prefix=/usr/local/openssl  
  3. ./config shared --prefix=/usr/local/openssl  
  4. make depend  
  5. make install  

     其他模块

        我们其它模块,都会使用到libcjson.so、libutils.so和libcrypto.so。对于这些第三方动态链接文件,我们需要在使用到他们的模块加载之前就加载它们。于是我们配置httpd.conf文件:[plain] view plaincopy
  1. LoadFile modules/libcjson.so   
  2. LoadFile modules/libutils.so  
  3. LoadFile modules/libcrypto.so  
  4.   
  5. LoadModule user_check_module  modules/mod_user_check.so  
  6. LoadModule login_module       modules/mod_login.so  
        如此,我们就可以在用户注册登录模块——mod_login.so和免登模块——mod_user_check.so中使用这些API了。

处理流程        

        在之前,我们说到,我们要是的免登逻辑位于所有请求之前。于是我们以注册和登录模块为例子,我们需要在httpd.conf中做如下配置[plain] view plaincopy
  1. <Location /login>  
  2.     SetHandler user_check  
  3.     SetHandler login  
  4. </Location>  
        user_check模块的代码如下
[cpp] view plaincopy
  1. static int user_check_handler(request_rec *r)  
  2. {  
  3.     char* uid;  
  4.     char* ss;  
  5.     char* did;  
  6.   
  7.     user_define_data_ptr = apr_palloc(r->pool, sizeof(user_data));  
  8.     user_data* user_data_ptr = (user_data*)r->user_define_data_ptr;  
  9.     user_data_ptr->login = 0;  
  10.       
  11.     uid = get_args_param(r, "uid");  
  12.     ss = get_args_param(r, "ss");  
  13.     did = get_args_param(r, "did");  
  14.     if (!uid || !ss || !did) {  
  15.         return DECLINED;  
  16.     }  
  17.   
  18.     user_data_ptr->login = !user_login_ok(r->pool, uid, ss, did);   
  19.     return DECLINED;  
  20. }  
        注意所有的返回值都是DECLINED。这样内容生成器,才会传导到下一个内容生成器中。        如果熟悉request_rec结构的同学,可能会马上对上面的代码产生疑问——哪儿来的user_define_data_ptr参数?是的,这个参数不是request_rec默认结构体的成员,是我为了贯通各个内容生成器自行加入的一个变量——修改/usr/local/apache2/include/httpd.h 
[cpp] view plaincopy
  1. /** 
  2.  * @brief A structure that represents the current request 
  3.  */  
  4. struct request_rec {  
  5.     /** The pool associated with the request */  
  6.     apr_pool_t *pool;  
  7.     ……  
  8.     /** MIME trailer environment from the response */  
  9.     apr_table_t *trailers_out;   
  10.     /** define by fangliang*/  
  11.     void* user_define_data_ptr;  
  12. };  
        请求通过免登模块检测后,便已经确认该用户是否已经登录了。然后在其他内容生成其中,通过user_define_data_ptr所指向的结构体对象得知其状态——上下文。        以上便将所有要点讲解完了,我们可以通过请求相关接口测试相应功能。

特殊问题

        我在链接Mysql数据库时,遇到了Access denied for user ''@'localhost'”的问题。在网上找到一个可行的解决方案,在此做以记录[plain] view plaincopy
  1. 1.关闭mysql  
  2.    # service mysqld stop  
  3. 2.屏蔽权限  
  4.    # mysqld_safe --skip-grant-table  
  5.    屏幕出现: Starting demo from .....  
  6. 3.新开起一个终端输入  
  7.    # mysql -u root mysql  
  8.    mysql> UPDATE user SET Password=PASSWORD('newpassword') where USER='root';  
  9.    mysql> FLUSH PRIVILEGES;//记得要这句话,否则如果关闭先前的终端,又会出现原来的错误  
  10.    mysql> /q  

代码片段

        以下列出比较有用的代码片段,方便大家使用。

    获取get请求中的参数

[cpp] view plaincopy
  1. char* get_args_param(request_rec* r, const char* name) {  
  2.     const char* args = r->args;  
  3.     const char* start_args;  
  4.     if (!args) {  
  5.         return NULL;  
  6.     }  
  7.   
  8.     for (start_args = ap_strstr_c(args, name); start_args;  
  9.             start_args = ap_strstr_c(start_args + 1, name))  
  10.     {  
  11.         if (start_args == args || start_args[-1] == '&' || isspace(start_args[-1])) {  
  12.             start_args += strlen(name);  
  13.             while (*start_args && isspace(*start_args)) {  
  14.                 ++start_args;  
  15.             }  
  16.             if (*start_args == '=' && start_args[1]) {  
  17.                 char* end_args;  
  18.                 char* arg;  
  19.                 ++start_args;  
  20.                 arg = apr_pstrdup(r->pool, start_args);  
  21.                 if ((end_args = strchr(arg, '&')) != NULL) {  
  22.                     *end_args = '/0';  
  23.                 }  
  24.                 return arg;  
  25.             }  
  26.         }  
  27.     }  
  28.     return NULL;  
  29. }  

    使用apr库实现md5算法

[cpp] view plaincopy
  1. unsigned char* md5hex(apr_pool_t* pool, const char* in, apr_size_t in_len) {  
  2.     unsigned char* out;  
  3.     apr_md5_ctx_t context;  
  4.     out = apr_palloc(pool, APR_MD5_DIGESTSIZE + 1);  
  5.     if (!out) {  
  6.         return NULL;  
  7.     }  
  8.   
  9.     if (0 != apr_md5_init(&context)) {  
  10.         return NULL;  
  11.     }  
  12.   
  13.     if (0 != apr_md5_update(&context, in, in_len)) {  
  14.         return NULL;  
  15.     }  
  16.   
  17.     if (0 != apr_md5_final(out, &context)) {  
  18.         return NULL;  
  19.     }  
  20.     out[APR_MD5_DIGESTSIZE] = '/0';  
  21.     return out;  
  22. };  
  23.   
  24. char hex2char(int hex) {  
  25.     char result = '/0';  
  26.     if(hex >= 0 && hex <= 9) {  
  27.         result = (char)(hex + 48);  
  28.     }  
  29.     else if(hex >= 10 && hex <= 15) {  
  30.         result = (char)(hex - 10 + 65);  
  31.     }  
  32.     else {  
  33.         result = (char)hex;  
  34.     }  
  35.     return result;  
  36. };  
  37.   
  38. char* md5(apr_pool_t* pool, const char* in, apr_size_t in_len) {  
  39.     char* out;  
  40.     unsigned char* md5buffer;  
  41.     out = apr_palloc(pool, APR_MD5_DIGESTSIZE * 2 + 1);  
  42.     if (!out) {  
  43.         return NULL;  
  44.     }  
  45.       
  46.     md5buffer = md5hex(pool, in, in_len);  
  47.     if (!md5buffer) {  
  48.         return NULL;  
  49.     }  
  50.   
  51.     for (apr_size_t index = 0; index < APR_MD5_DIGESTSIZE; index++) {  
  52.         unsigned char high;  
  53.         unsigned char low;  
  54.         unsigned char tmp;  
  55.         high = md5buffer[index] >> 4;  
  56.         tmp = md5buffer[index] << 4;  
  57.         low = tmp >> 4;  
  58.         out[2 * index] = hex2char(high);  
  59.         out[2 * index + 1] = hex2char(low);  
  60.     }  
  61.     out[APR_MD5_DIGESTSIZE * 2] = '/0';  
  62.     return out;  
  63. };  

    aes128加解密算法

[cpp] view plaincopy
  1. #include "openssl/evp.h"  
  2.   
  3. apr_size_t aes_128_encrypt(  
  4.         apr_pool_t* pool,  
  5.         const unsigned char* in,  
  6.         apr_size_t in_len,  
  7.         const unsigned char* key,  
  8.         const unsigned char* iv,  
  9.         unsigned char** out)  
  10. {  
  11.     EVP_CIPHER_CTX  *ctx;  
  12.     int len;  
  13.     apr_size_t  out_len = 0;  
  14.   
  15.     if (!(ctx = EVP_CIPHER_CTX_new())) {  
  16.         return 0;  
  17.     }  
  18.   
  19.     if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) {  
  20.         return 0;  
  21.     }  
  22.       
  23.     len = (in_len / 16 + 1) *16;  
  24.     *out = apr_palloc(pool, len);  
  25.     if(1 != EVP_EncryptUpdate(ctx, *out, &len, in, in_len)) {  
  26.         return 0;  
  27.     }  
  28.     out_len = len;  
  29.       
  30.     if(1 != EVP_EncryptFinal_ex(ctx, *out + len, &len)) {  
  31.         return 0;  
  32.     }  
  33.     out_len += len;  
  34.   
  35.     EVP_CIPHER_CTX_free(ctx);  
  36.   
  37.     return out_len;  
  38. };  
  39.   
  40. apr_size_t aes_128_decrypt(  
  41.         apr_pool_t* pool,  
  42.         const unsigned char* in,  
  43.         apr_size_t in_len,  
  44.         const unsigned char* key,  
  45.         const unsigned char* iv,  
  46.         unsigned char** out)  
  47. {  
  48.     EVP_CIPHER_CTX  *ctx;  
  49.     int len;  
  50.     apr_size_t  out_len = 0;  
  51.   
  52.     if (!(ctx = EVP_CIPHER_CTX_new())) {  
  53.         return 0;  
  54.     }  
  55.   
  56.     if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) {  
  57.         return 0;  
  58.     }  
  59.   
  60.     len = (in_len / 16 + 1) *16;  
  61.     *out = apr_palloc(pool, len);  
  62.     if(1 != EVP_DecryptUpdate(ctx, *out, &len, in, in_len)) {  
  63.         return 0;  
  64.     }  
  65.     out_len = len;  
  66.   
  67.     if(1 != EVP_DecryptFinal_ex(ctx, *out + len, &len)) {  
  68.         return 0;  
  69.     }  
  70.     out_len += len;  
  71.   
  72.     EVP_CIPHER_CTX_free(ctx);  
  73.   
  74.     return out_len;  
  75. };  

    更新数据库中的项

[cpp] view plaincopy
  1. apr_status_t updata_db(apr_pool_t* pool, const char* table_name,  
  2.         const char* uid, const char* col_name, const char* value)  
  3. {  
  4.     const apr_dbd_driver_t* driver = NULL;  
  5.     apr_dbd_t* handle = NULL;  
  6.     apr_dbd_results_t* res = NULL;  
  7.     char* sql_cmd;  
  8.     apr_status_t status;  
  9.     int nrows;  
  10.     if (!pool || !uid || !table_name || !col_name || !value) {  
  11.         return 1;  
  12.     }  
  13.   
  14.     status = apr_dbd_get_driver(pool, "mysql", &driver);  
  15.     if (APR_SUCCESS != status) {  
  16.         return 1;  
  17.     }  
  18.   
  19.     status = apr_dbd_open(driver, pool, "host=localhost;user=root;pass=password;dbname=database_name", &handle);  
  20.     if (APR_SUCCESS != status) {  
  21.         return 1;  
  22.     }  
  23.   
  24.     sql_cmd = apr_psprintf(pool, "update %s set %s=%s where userid='%s'", table_name, col_name, value, uid);  
  25.     status = apr_dbd_query(driver, handle, &nrows, sql_cmd);  
  26.     if (APR_SUCCESS != status && 1 != nrows) {  
  27.         status = 1;  
  28.     }  
  29.     else {  
  30.         status = 0;  
  31.     }  
  32.     apr_dbd_close(driver, handle);  
  33.   
  34.     return status;  
  35. }  

    获取数据库中某项

[cpp] view plaincopy
  1. char* get_value(apr_pool_t* pool, const char* uid, const char* col_name) {  
  2.     const apr_dbd_driver_t* driver = NULL;  
  3.     apr_dbd_t* handle = NULL;  
  4.     apr_dbd_results_t* res = NULL;  
  5.     char* sql_cmd;  
  6.     apr_dbd_row_t* row;  
  7.     apr_status_t status;  
  8.     char* value = NULL;  
  9.     const char* value_tmp = NULL;  
  10.     if (!pool || !uid) {  
  11.         return NULL;  
  12.     }  
  13.   
  14.     status = apr_dbd_get_driver(pool, "mysql", &driver);  
  15.     if (APR_SUCCESS != status) {  
  16.         return NULL;  
  17.     }  
  18.   
  19.     status = apr_dbd_open(driver, pool, "host=localhost;user=root;pass=password;dbname=database_name", &handle);  
  20.     if (APR_SUCCESS != status) {  
  21.         return NULL;  
  22.     }  
  23.   
  24.     sql_cmd = apr_psprintf(pool, "select %s from userlogin where userid='%s'", col_name, uid);  
  25.     status = apr_dbd_select(driver, pool, handle, &res, sql_cmd, 0);  
  26.     if (APR_SUCCESS != status) {  
  27.         value = NULL;  
  28.     }  
  29.       
  30.     if (0 == apr_dbd_get_row(driver, pool, res, &row, 1)) {  
  31.         value_tmp = apr_dbd_get_entry(driver, row, 0);  
  32.         value = apr_palloc(pool, 128);  
  33.         strcpy(value, value_tmp);  
  34.     }  
  35.     else {  
  36.         value = NULL;  
  37.     }  
  38.     apr_dbd_close(driver, handle);  
  39.   
  40.     return value;  
  41. };  

        最后附上模块的代码地址链接: http://pan.baidu.com/s/1dDmAmvZ 密码: c28d


上文来自:http://blog.csdn.net/breaksoftware/article/details/48242243



相关阅读:
Top