服务器架设笔记——搭建用户注册和验证功能
之前介绍的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):
- 注册和登陆;
- 免登;
- 公用库;
- json库;这儿需要说明下,我们将使用json作为返回参数的格式。虽然XML是apache apr库中一个可用模块,也是可以用来组织返回数据,但是json还是更加主流。
Json库
因为apache httpd是C语言写的,所以为了统一风格以及免除之后一切编译相关的问题,我选在了同样的C写的json库——CJson。我们只选用cJSON.h和cJson.c两个文件作为库的原始文件,并编写一个编译脚本
gcc cJSON.c -fPIC -lm -shared -o libcjson.so
cp libcjson.so /usr/local/apache2/modules/
基础库——utils
我将基础库分为如下几个功能集:
- 加解密;在登陆校验等业务中会使用到。
- 编码;base64编码和解码是服务的基础功能。
- hash;md5等是必要功能。
- 其他辅助函数;一些函数比较复杂,在多个模块中都要被使用到,所以把他们放到基础库中,供各个模块使用。
编码和Hash没什么好说的,apr库里提供了便捷的方法。加解密在apr-util里也有相应的封装。这儿需要指出的是,我们在编译apr-util时需要指定参数--with-crypto。有的文章上说,还要通过--with-openssl来指定使用openssl库。而我试验发现通过指定该参数,反而会导致加解密模块不可用。因为我们还要使用数据库,所以我们如此编译apr-util
./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr --with-crypto --with-mysql
我们通过查看/usr/local/apr-util/include/apr-1/apu.h文件中相应宏的定义来知晓相应功能是否已被启用
#define APU_HAVE_PGSQL 0
#define APU_HAVE_MYSQL 1
#define APU_HAVE_SQLITE3 0
#define APU_HAVE_SQLITE2 0
#define APU_HAVE_ORACLE 0
#define APU_HAVE_FREETDS 0
#define APU_HAVE_ODBC 0#define APU_HAVE_CRYPTO 1
#define APU_HAVE_OPENSSL 1
#define APU_HAVE_NSS 0
但是非常不幸的是,我参考例子写的一段加解密代码,在测试代码中可以正确运行,而在请求线程中却出现了一些诡异的现象。于是我只能直接使用openssl中的API进行加解密。
使用如下指令编译openssl,将产出动态链接库,我们将libcrypto.so拷贝到apache httpd的module目录下。
make clean
./config --prefix=/usr/local/openssl
./config shared --prefix=/usr/local/openssl
make depend
make install
其他模块
我们其它模块,都会使用到libcjson.so、libutils.so和libcrypto.so。对于这些第三方动态链接文件,我们需要在使用到他们的模块加载之前就加载它们。于是我们配置httpd.conf文件:
LoadFile modules/libcjson.so
LoadFile modules/libutils.so
LoadFile modules/libcrypto.soLoadModule user_check_module modules/mod_user_check.so
LoadModule login_module modules/mod_login.so
如此,我们就可以在用户注册登录模块——mod_login.so和免登模块——mod_user_check.so中使用这些API了。
处理流程
在之前,我们说到,我们要是的免登逻辑位于所有请求之前。于是我们以注册和登录模块为例子,我们需要在httpd.conf中做如下配置
<Location /login>SetHandler user_checkSetHandler login
</Location>
user_check模块的代码如下
static int user_check_handler(request_rec *r)
{char* uid;char* ss;char* did;user_define_data_ptr = apr_palloc(r->pool, sizeof(user_data));user_data* user_data_ptr = (user_data*)r->user_define_data_ptr;user_data_ptr->login = 0;uid = get_args_param(r, "uid");ss = get_args_param(r, "ss");did = get_args_param(r, "did");if (!uid || !ss || !did) {return DECLINED;}user_data_ptr->login = !user_login_ok(r->pool, uid, ss, did); return DECLINED;
}
注意所有的返回值都是DECLINED。这样内容生成器,才会传导到下一个内容生成器中。
如果熟悉request_rec结构的同学,可能会马上对上面的代码产生疑问——哪儿来的user_define_data_ptr参数?是的,这个参数不是request_rec默认结构体的成员,是我为了贯通各个内容生成器自行加入的一个变量——修改/usr/local/apache2/include/httpd.h
/*** @brief A structure that represents the current request*/
struct request_rec {/** The pool associated with the request */apr_pool_t *pool;……/** MIME trailer environment from the response */apr_table_t *trailers_out; /** define by fangliang*/void* user_define_data_ptr;
};
请求通过免登模块检测后,便已经确认该用户是否已经登录了。然后在其他内容生成其中,通过user_define_data_ptr所指向的结构体对象得知其状态——上下文(如果不想修改源码,可以考虑使用apr_pool_userdata_setn和apr_pool_userdata_get的组合)。
以上便将所有要点讲解完了,我们可以通过请求相关接口测试相应功能。
特殊问题
我在链接Mysql数据库时,遇到了Access denied for user ''@'localhost'”的问题。在网上找到一个可行的解决方案,在此做以记录
1.关闭mysql# service mysqld stop
2.屏蔽权限# mysqld_safe --skip-grant-table屏幕出现: Starting demo from .....
3.新开起一个终端输入# mysql -u root mysqlmysql> UPDATE user SET Password=PASSWORD('newpassword') where USER='root';mysql> FLUSH PRIVILEGES;//记得要这句话,否则如果关闭先前的终端,又会出现原来的错误mysql> \q
代码片段
以下列出比较有用的代码片段,方便大家使用。
获取get请求中的参数
char* get_args_param(request_rec* r, const char* name) {const char* args = r->args;const char* start_args;if (!args) {return NULL;}for (start_args = ap_strstr_c(args, name); start_args;start_args = ap_strstr_c(start_args + 1, name)){if (start_args == args || start_args[-1] == '&' || isspace(start_args[-1])) {start_args += strlen(name);while (*start_args && isspace(*start_args)) {++start_args;}if (*start_args == '=' && start_args[1]) {char* end_args;char* arg;++start_args;arg = apr_pstrdup(r->pool, start_args);if ((end_args = strchr(arg, '&')) != NULL) {*end_args = '\0';}return arg;}}}return NULL;
}
使用apr库实现md5算法
unsigned char* md5hex(apr_pool_t* pool, const char* in, apr_size_t in_len) {unsigned char* out;apr_md5_ctx_t context;out = apr_palloc(pool, APR_MD5_DIGESTSIZE + 1);if (!out) {return NULL;}if (0 != apr_md5_init(&context)) {return NULL;}if (0 != apr_md5_update(&context, in, in_len)) {return NULL;}if (0 != apr_md5_final(out, &context)) {return NULL;}out[APR_MD5_DIGESTSIZE] = '\0';return out;
};char hex2char(int hex) {char result = '\0';if(hex >= 0 && hex <= 9) {result = (char)(hex + 48);}else if(hex >= 10 && hex <= 15) {result = (char)(hex - 10 + 65);}else {result = (char)hex;}return result;
};char* md5(apr_pool_t* pool, const char* in, apr_size_t in_len) {char* out;unsigned char* md5buffer;out = apr_palloc(pool, APR_MD5_DIGESTSIZE * 2 + 1);if (!out) {return NULL;}md5buffer = md5hex(pool, in, in_len);if (!md5buffer) {return NULL;}for (apr_size_t index = 0; index < APR_MD5_DIGESTSIZE; index++) {unsigned char high;unsigned char low;unsigned char tmp;high = md5buffer[index] >> 4;tmp = md5buffer[index] << 4;low = tmp >> 4;out[2 * index] = hex2char(high);out[2 * index + 1] = hex2char(low);}out[APR_MD5_DIGESTSIZE * 2] = '\0';return out;
};
aes128加解密算法
#include "openssl/evp.h"apr_size_t aes_128_encrypt(apr_pool_t* pool,const unsigned char* in,apr_size_t in_len,const unsigned char* key,const unsigned char* iv,unsigned char** out)
{EVP_CIPHER_CTX *ctx;int len;apr_size_t out_len = 0;if (!(ctx = EVP_CIPHER_CTX_new())) {return 0;}if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) {return 0;}len = (in_len / 16 + 1) *16;*out = apr_palloc(pool, len);if(1 != EVP_EncryptUpdate(ctx, *out, &len, in, in_len)) {return 0;}out_len = len;if(1 != EVP_EncryptFinal_ex(ctx, *out + len, &len)) {return 0;}out_len += len;EVP_CIPHER_CTX_free(ctx);return out_len;
};apr_size_t aes_128_decrypt(apr_pool_t* pool,const unsigned char* in,apr_size_t in_len,const unsigned char* key,const unsigned char* iv,unsigned char** out)
{EVP_CIPHER_CTX *ctx;int len;apr_size_t out_len = 0;if (!(ctx = EVP_CIPHER_CTX_new())) {return 0;}if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv)) {return 0;}len = (in_len / 16 + 1) *16;*out = apr_palloc(pool, len);if(1 != EVP_DecryptUpdate(ctx, *out, &len, in, in_len)) {return 0;}out_len = len;if(1 != EVP_DecryptFinal_ex(ctx, *out + len, &len)) {return 0;}out_len += len;EVP_CIPHER_CTX_free(ctx);return out_len;
};
更新数据库中的项
apr_status_t updata_db(apr_pool_t* pool, const char* table_name,const char* uid, const char* col_name, const char* value)
{const apr_dbd_driver_t* driver = NULL;apr_dbd_t* handle = NULL;apr_dbd_results_t* res = NULL;char* sql_cmd;apr_status_t status;int nrows;if (!pool || !uid || !table_name || !col_name || !value) {return 1;}status = apr_dbd_get_driver(pool, "mysql", &driver);if (APR_SUCCESS != status) {return 1;}status = apr_dbd_open(driver, pool, "host=localhost;user=root;pass=password;dbname=database_name", &handle);if (APR_SUCCESS != status) {return 1;}sql_cmd = apr_psprintf(pool, "update %s set %s=%s where userid='%s'", table_name, col_name, value, uid);status = apr_dbd_query(driver, handle, &nrows, sql_cmd);if (APR_SUCCESS != status && 1 != nrows) {status = 1;}else {status = 0;}apr_dbd_close(driver, handle);return status;
}
获取数据库中某项
char* get_value(apr_pool_t* pool, const char* uid, const char* col_name) {const apr_dbd_driver_t* driver = NULL;apr_dbd_t* handle = NULL;apr_dbd_results_t* res = NULL;char* sql_cmd;apr_dbd_row_t* row;apr_status_t status;char* value = NULL;const char* value_tmp = NULL;if (!pool || !uid) {return NULL;}status = apr_dbd_get_driver(pool, "mysql", &driver);if (APR_SUCCESS != status) {return NULL;}status = apr_dbd_open(driver, pool, "host=localhost;user=root;pass=password;dbname=database_name", &handle);if (APR_SUCCESS != status) {return NULL;}sql_cmd = apr_psprintf(pool, "select %s from userlogin where userid='%s'", col_name, uid);status = apr_dbd_select(driver, pool, handle, &res, sql_cmd, 0);if (APR_SUCCESS != status) {value = NULL;}if (0 == apr_dbd_get_row(driver, pool, res, &row, 1)) {value_tmp = apr_dbd_get_entry(driver, row, 0);value = apr_palloc(pool, 128);strcpy(value, value_tmp);}else {value = NULL;}apr_dbd_close(driver, handle);return value;
};
最后附上模块的代码地址链接: http://pan.baidu.com/s/1dDmAmvZ 密码: c28d
相关文章:

项目管理过程中应注意的问题
软件项目从角色分工方面可以划分为研发、开发和实施三类,每个类型的项目有各自的管理过程。下面笔者就公司实施类项目的经历,从项目经理的角度谈一谈实施类项目管理过程中应该注意的一些问题,希望大家共勉。确定项目概况俗话说:“…
原创jQuery移动设备弹出框插件——msgalert.js
最近开发经常会用到顶部弹出框,虽然有现成的(bootstrap等),但是都很臃肿,对于有些时候移动端活动页面有点大材小用。所以今晚花了20分钟写了一个通用的插件,我将其命名为msgalert.js。因为定位是jQuery插件,…
AbutionGraph:构建以知识图谱为核心的下一代数据中台
「免费学习 60 节公开课:投票页面,点击讲师头像」作者 | 图特摩斯科技创始人闭雨哲出品 | AI科技大本营(ID:rgznai100)前言图特摩斯科技(Thutmose)基于自研的图形数据库AbutionGraph(实时多维数…

服务器架设笔记——多模块和全局数据
随着项目工程的发展,多模块设计和性能优化是在所难免的。本文我将基于一些现实中可能遇到的需求,讲解如何在Apache的Httpd插件体系中实现这些功能。(转载请指明出于breaksoftware的csdn博客) 之前我碰到两个需求: 需要…

JSP学习笔记(七):使用JavaBean
bean.java publicclassB1 { publicString getString() { return"content"; } }page.jsp <%B1 b1 newB1(); out.print(b1.getString());%>

折返(Reentrancy)VS线程安全(Thread safety)
在Wiki上,折返例如,下面的定义(接) In computing, a computer program or subroutine is called reentrant if it can be interrupted in the middle of its execution and then safely called again ("re-entered") be…
服务器架设笔记——httpd插件支持mysql字符集选择
mysql数据库默认的字符集是latin1。默认情况下,我们编译的httpd插件是可以正常读取该类型的数据库,并且不会出现乱码。但是,如果我们的数据库变成其他格式,比如UTF8,那么默认读取出来的数据就是乱码,且无论…
只需3行代码自动生成高性能模型,支持4项任务,亚马逊发布开源库AutoGluon
作者 | KYLE WIGGERS编译 | AI科技大本营(ID:rgznai100)构建涉及图像、文本和表格数据集的机器学习应用并不容易。它需要特征工程或使用数据领域知识来创建使AI算法起作用的特征,还需要进行大量数据预处理,以确保训练模型时不会出…

在客户端(IE中)无法登录Citrix MetaFrame server的原因
当在IE中登录服务器时如果出现这面的错误提示: ERROR: The Citrix MetaFrame servers cannot process your request at this time. The Citrix XML Service object was not found. [404 Not Found] 我的原因是IE使用了8080端口,而我的Citrix XML servic…

虚拟化--015 配置VMware View Event database失败:
015 配置VMware View Event database失败:参考链接http://url.cn/VTq4zN 转载于:https://blog.51cto.com/williamliuwen/1686536
以金山界面库(openkui)为例思考和分析界面库的设计和实现——代码结构(完)
三年前,准备将金山界面库做一个全面的剖析。后来由于种种原因,这个系列被中断而一直没有更新。时过境迁,现在在windows上从事开发的人员越来越少,关注这块的技术的朋友也很少了。本以为这系列也随着技术的没落而不再被人所关注&am…
一包烟钱买到电动剃须刀,小米有品告诉你什么叫性价比
男人身上长得最快的是什么?答案是胡须。一名健康男性的胡须每天都要生长超过 0.4mm,比咱们头发的生长速度还快,这也是男人隔三差五就要剃须的原因之一。男人的一生是与胡子战斗一生,也是被剃须刀拖累的一生。出差办事儿࿰…

Label控件属性AssociatedControlID
可以使用Label控件来标注一个HTML表单字段。Label控件拥有属性AssociatedControlID,可以设置此属性来指向表示表单字段的ASP.NET控件。 例如,代码清单2-3中的页面含有一个简单的表单,表单包含两个字段用于输入名和姓。Label控件用于标注这两个TextBox控件。 Code<% Page Lan…

2015_8_21作业——有自翻译有复制他人的英语太差
date作用:打印或设置系统日期和时间格式:date [OPTION]...[FORMAT]date [u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]|是或 多选一的选项 ...代表同类内容可多次选项:注意短选项后不可加号-d,--dateSTRING显示时间字符串但不是立即:如date…
WMI技术介绍和应用——接收事件
时隔两三年,再次更新WMI系列博文。好在功能在三年前就已经实现了,现在只要补充些实例即可。 之前介绍的基本都是查询静态数据,而本文将要介绍非常有意思的事件接收功能。(转载请指明出于breaksoftware的csdn博客) 监控…

WML标签速查手册
WML标签速查手册 结构相关标签语法及属性<wml><wml xml:lang"lang" > content</wml> WML元素的共有属性主要有3个,即id、class和xml:lang属性。WML的所有元素都有两个核心属性,即标识(id)和类(class)属性。它们主要用…
Python 三十大实践、建议和技巧
所有参与投票的 CSDN 用户都参加抽奖活动群内公布奖项,还有更多福利赠送作者 | Erik-Jan van Baaren译者 | 凯隐编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导读】2020年,你又立了什么新的 Flag?新一年…

点击通知栏后打开Activity,并传参
为什么80%的码农都做不了架构师?>>> Reciver中intent new Intent(context, MessageDetailsaActivity_.class);intent.putExtra("freshMessageId", String.valueOf(push.getObid()));intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Inten…

WMI技术介绍和应用——执行方法
在之前的博文中,我们主要介绍了如何使用WMI查询信息和接收事件。本文将介绍WMI的另一种用法——执行方法。(转载请指明出于breaksoftware的csdn博客) 这块的内容在msdn中有详细的介绍,如果想看原版的可以参阅《Example: Calling a…

动态指定超链接参数的几种方法(Passing a JavaScript variable into href of )
情景:有些环境下我们需要根据页面中JavaScript变量的值来确定某个(某些)超链接的参数,如“http://www.bla.com/test.aspx?var1”中,究竟var1等于多少,要根据JavaScript变量来判定 方法一:很简单…

知乎「致知计划之科学季」颁奖,创作者分享80万元奖金
1月11日,「致知计划之科学季」颁奖典礼在北京798艺术中心举行,近500名创作者到场参加。 为了激励创作者,推动优质内容持续产出,知乎的「致知计划」从四个方面对创作者进行了扶持。一是流量扶持,让专业、优质的内容获得…

Inno Setup制作应用程序安装包
我最近写了一个MFC应用程序,想发给其他的小伙伴玩一玩,直接发了个exe文件过去,结果发现小伙伴那边打不开。原来这个exe文件虽然是MFC静态编译的,但是还依赖了其他几个.dll文件,需要把这几个dll文件和exe文件放在同一个…

WMI技术介绍和应用——事件通知
在《WMI技术介绍和应用——WMI概述》中,我们使用了下图介绍WMI构架(转载请指明出于breaksoftware的csdn博客) 我们之前介绍的使用WMI查询系统、硬件等信息的功能,是通过查询WMI静态数据的空间实现的。这个功能的核心是在上图中2&a…

OpenWebSpider 安装使用
OpenWebSpider 是一个很好用的网络爬虫,也可以叫做“网络蜘蛛”. 安装: 1.使用Vs.net 2003 编译, 记得要copy libmysql.dll到工程里 2.配置文件openwebspider.conf 将已经启动了的 mysql数据库用户名,密码填写好 3.数据库建立 执行 sql_struc…
杂谈 | 当前知识蒸馏与迁移学习有哪些可用的开源工具?
所有参与投票的 CSDN 用户都参加抽奖活动群内公布奖项,还有更多福利赠送作者&编辑 | 言有三来源 | 有三AI(ID:yanyousan_ai)【导读】知识蒸馏与迁移学习不仅仅属于模型优化的重要技术之一,也是提升模型跨领域泛化能力的重要技…

对 Thinking in java 4th Edition I/O DirList.java的疑问
2019独角兽企业重金招聘Python工程师标准>>> 以下原文代码: //: io/DirList.java // Display a directory listing using regular expressions. // {Args: "D.*\.java"} import java.util.regex.*; import java.io.*; import java.util.*; pub…

WMI技术介绍和应用——Instance/Method Provider
在《WMI技术介绍和应用——事件通知》一文中,我们提到了提供者(Provider)这个概念。我们还是要引入WMI的结构图(转载请指明出于breaksoftware的csdn博客) 我们在1这层的Native C/C里可以看到若干Provider,这…

SSH 组建轻量级架构 附录 -- 遇到的问题和解答
action: nulljava.lang.ClassNotFoundException: org.springframework.web.struts.ContextLoaderPlugIn解决方法:加载 spring.jar 包 报 无法初始化at org.apache.struts.action.ActionServlet.initModulePlugIns(解决方法:删除 asm-2.2.3.jar springda…
TIOBE 1月编程语言排行榜:C语言再度「C 位」出道,Python惜败
整理 | 屠敏来源 | CSDN(ID:CSDNnews)【导读】在 2020 年初雪来临之际,TIOBE 官方在最新发布的 1 月编程语言榜单中为我们最终揭开了「 2019 年度编程语言」的神秘面纱,然意料之外情理之中,获此殊荣的并非是…

my项目的总结2015.8.26编
这已经是上上个星期的事了,现在回顾一下: 负责的模块是"my",更精准的说应该是my里面的个人信息管理 由于项目分域,模块已经分好了,涉及到的只是在现有的基础上解决分域后遗留的历史问题 上点图吧:…