本节将使用PHP和Redis实现用户注册登录功能,下面分模块来介绍具体实现方法。

1.注册

需求描述:用户注册时需要提交邮箱、登录密码和昵称。其中邮箱是用户的唯一标识,每个用户的邮箱不能重复,但允许用户修改自己的邮箱。

我们使用散列类型来存储用户的资料,键名为user:用户ID。其中用户ID是一个自增的数字,之所以使用 ID 而不是邮箱作为用户的标识是因为考虑到在其他键中可能会通过用户的标识与用户对象相关联,如果使用邮箱作为用户的标识的话在用户修改邮箱时就不得不同时需要修改大量的键名或键值。为了尽可能地减少要修改的地方,我们只把邮箱作为该散列键的一个字段。为此还需要使用一个散列类型的键email.to.id来记录邮箱和用户ID间的对应关系以便在登录时能够通过邮箱获得用户的ID。

用户填写并提交注册表单后首先需要验证用户输入,我们在项目目录中建立一个register.php文件来实现用户注册的逻辑。验证部分的代码如下:

// 设置Content-type以使浏览器可以使用正确的编码显示提示信息,

// 具体的编码需要根据文件实际编码选择,此处是utf-8。

header("Content-type: text/html; charset=utf-8");

if(!isset($_POST['email']) ||

!isset($_POST['password']) ||

!isset($_POST['nickname'])) {

echo '请填写完整的信息。';

exit;

}

$email = $_POST['email'];

// 验证用户提交的邮箱是否正确

if(!filter_var($email, FILTER_VALIDATE_EMAIL)) {

echo '邮箱格式不正确,请重新检查';

exit;

}

$rawPassword = $_POST['password'];

// 验证用户提交的密码是否安全

if(strlen($rawPassword) < 6) {

echo '为了保证安全,密码长度至少为6。';

exit;

}

$nickname = $_POST['nickname'];

//不同的网站对用户昵称有不同的要求,这里不再做检查,即使是空也可以。

// 而后我们需要判断用户提交的邮箱是否被注册了:

$redis = new Predis\Client();

if($redis->hexists('email.to.id', $email)) {

echo '该邮箱已经被注册过了。';

exit;

}

验证通过后接下来就需要将用户资料存入Redis中。在存储的时候要记住使用散列函数处理用户提交的密码,避免在数据库中存储明文密码。原因是如果数据库中数据泄露(外部原因或内部原因都有可能),攻击者也无法获得用户的真实密码,也便无法正常地登录进系统。更重要的是考虑到用户很可能在其他网站中也使用了同样的密码,所以明文密码泄露还会给用户造成额外的损失。

除此之外,还要避免使用速度较快的散列函数处理密码以防止攻击者使用穷举法破解密码,并且需要为每个用户生成一个随机的“盐”(salt)以避免攻击者使用彩虹表破解。这里作为示例,我们使用Bcrypt算法来对密码进行散列。PHP 5.3中提供的crypt函数支持Bcrypt算法,我们可以实现一个函数来随机生成盐并调用crypt函数获得散列后的密码:

function bcryptHash($rawPassword, $round = 8)

{

if ($round < 4 || $round > 31) $round = 8;

$salt = '$2a$' . str_pad($round, 2, '0', STR_PAD_LEFT) . '$';

$randomValue = openssl_random_pseudo_bytes(16);

$salt .= substr(strtr(base64_encode($randomValue), '+', '.'),  0, 22);

return crypt($rawPassword, $salt);

}

提示  openssl_random_pseudo_bytes函数需要安装OpenSSL扩展。

之后使用如下代码获得散列后的密码:

$hashedPassword = bcryptHash($rawPassword);

存储用户资料就很简单了,所有命令都在第3章介绍过了。代码如下:

require './predis/autoload.php';

$redis = new Predis\Client();

// 首先获取一个自增的用户ID

$userID = $redis->incr('users:count');

// 存储用户信息

$redis->hmset("user:{$userID}", array(

'email'  => $email,

'password'   => $hashedPassword,

'nickname'   => $nickname

));

// 记得记录下邮箱和用户ID的对应关系

$redis->hset('email.to.id', $email, $userID);

// 提示用户注册成功

echo '注册成功!';

大部分情况下在注册时我们需要验证用户的邮箱,不过这部分的逻辑与忘记密码部分相似,所以在这里不做更多的介绍。

2.登录

需求描述:用户登录时需要提交邮箱和登录密码,如果正确则输出“登录成功”,否则输出“用户名或密码错误”。

当用户提交邮箱和登录密码后首先通过email.to.id键获得用户ID,然后将用户提交的登录密码使用同样的盐进行散列并与数据库存储的密码比对,如果一样则表示登录成功。我们新建一个login.php文件来处理用户的登录,处理该逻辑的部分代码如下:

header("Content-type: text/html; charset=utf-8");

if(!isset($_POST['email']) ||

!isset($_POST['password'])) {

echo '请填写完整的信息。';

exit;

}

$email = $_POST['email'];

$rawPassword = $_POST['password'];

require './predis/autoload.php';

$redis = new Predis\Client();

// 获得用户的ID

$userID = $redis->hget('email.to.id', $email);

if(!$userID) {

echo '用户名或密码错误。';

exit;

}

$hashedPassword = $redis->hget("user:{$userID}", 'password');

现在我们得到了之前存储过的经过散列后的密码,接着定义一个函数来对用户提交的密码进行散列处理。bcryptHash函数中返回的密码中已经包含了盐,所以只需要直接将散列后的密码作为crypt函数的第二个参数,crypt函数会自动地提取出密码中的盐:

function bcryptVerify($rawPassword, $storedHash)

{

return crypt($rawPassword, $storedHash) == $storedHash;

}

之后就可以使用此函数进行比对了:

if(!bcryptVerify($rawPassword, $hashedPassword)) {

echo '用户名或密码错误。';

exit;

}

echo '登录成功!';

3.忘记密码

需求描述:当用户忘记密码时可以输入自己的邮箱,系统会发送一封包含更改密码的链接的邮件,用户单击该链接后会进入密码修改页面。该模块的访问频率限制为1分钟10次以防止恶意用户通过此模块向某个邮箱地址大量发送垃圾邮件。

当用户在忘记密码的页面输入邮箱后,我们的程序需要做两件事。

(1)进行访问频率限制。这里使用4.2.3节介绍的方法以邮箱为标示符对发送修改密码邮件的过程进行访问频率限制。当用户提交了邮箱地址后首先验证邮箱地址是否正确,如果正确则检查访问频率是否超限:

$keyName = "rate.limiting:{$email}";

$now = time();

if($redis->llen($keyName) < 10) {

$redis->lpush($keyName, $now);

} else {

$time = $redis->lindex($keyName, -1);

if($now - $time < 60) {

echo '访问频率超过了限制,请稍后再试。';

exit;

} else {

$redis->lpush($keyName, $now);

$redis->ltrim($keyName, 0, 9);

}

}

一般在全站中还会有针对IP地址的访问频率限制,原理与此类似。

(2)发送修改密码邮件。用户通过访问频率限制后我们会为其生成一个随机的验证码,并将验证码通过邮件发送给用户。同时在程序中要把用户的邮箱地址存入名为retrieve.password.code:散列后的验证码的字符串类型键中,然后使用EXPIRE命令为其设置一个生存时间(如1个小时)以提供安全性并且保证及时释放存储空间。由于忘记密码需要的安全等级与用户注册登录相同,所以我们依然使用Bcrypt算法来对验证码进行散列,具体的算法同上这里不再详述。

redis实践:用户注册登录功能的更多相关文章

  1. cookie理解与实践【实现简单登录以及自动登录功能】

    cookie理解 Cookie是由W3C组织提出,最早由netscape社区发展的一种机制 http是无状态协议.当某次连接中数据提交完,连接会关闭,再次访问时,浏览器与服务器需要重新建立新的连接: ...

  2. XMPP系列(二)----用户注册和用户登录功能

    1.创建一个新工程 2.导入XMPP框架 最新的XMPP框架下载地址:https://github.com/robbiehanson/XMPPFramework 将XMPP的几个文件夹拖进工程中,需要 ...

  3. 重学 Java 设计模式:实战装饰器模式(SSO单点登录功能扩展,增加拦截用户访问方法范围场景)

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 对于代码你有编程感觉吗 很多人写代码往往是没有编程感觉的,也就是除了可以把功能按照固 ...

  4. 节约内存:Instagram的Redis实践(转)

    一.问题:     数据库表数据量极大(千万条),要求让服务器更加快速地响应用户的需求. 二.解决方案:      1.通过高速服务器Cache缓存数据库数据      2.内存数据库 三.主流解Ca ...

  5. 通过Keepalived实现Redis Failover自动故障切换功能

    通过Keepalived实现Redis Failover自动故障切换功能[实践分享] 参考资料: http://patrick-tang.blogspot.com/2012/06/redis-keep ...

  6. 传智播客JavaWeb day07、day08-自定义标签(传统标签和简单标签)、mvc设计模式、用户注册登录注销

    第七天的课程主要是讲了自定义标签.简单介绍了mvc设计模式.然后做了案例 1. 自定义标签 1.1 为什么要有自定义标签 前面所说的EL.JSTL等技术都是为了提高jsp的可读性.可维护性.方便性而取 ...

  7. Redis 实践笔记

    本文来自:http://www.cnblogs.com/me-sa/archive/2012/03/13/redis-in-action.html 最近在项目中实践了一下Redis,过程中遇到并解决了 ...

  8. 《微信小程序七日谈》- 第五天:你可能要在登录功能上花费大力气

    <微信小程序七日谈>系列文章: 第一天:人生若只如初见: 第二天:你可能要抛弃原来的响应式开发思维: 第三天:玩转Page组件的生命周期: 第四天:页面路径最多五层?导航可以这么玩: 第五 ...

  9. 6 Django REST framework JWT 和登录功能实现

    JWT 在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证. 我们不再使用Session认证机制,而使用Json Web Token认证机制. Json web token ( ...

随机推荐

  1. 我所认识的PCA算法的princomp函数与经历 (基于matlab)

    我接触princomp函数,主要是因为实验室的项目需要,所以我一接触的时候就希望快点学会怎么用. 项目中需要利用PCA算法对大量数据进行降维. 简介:主成分分析 ( Principal Compone ...

  2. java基础-四大特征

  3. 用Bluepages来验证intranetId和Password的有效性

    代码很简单,如下: int ret = -1;ReturnCode rc = null;            cwa2 cw = new cwa2();rc = cw.authenticate(in ...

  4. 解决Synergy的鼠标无法从服务器(server)机屏幕移动到客户机(client)屏幕的问题

    我在工作时使用一台Win 7笔记本和一台Ubuntu台式机,为了提升工作效率,我使用Synergy在两台机器间共享了笔记本的鼠标和键盘,即笔记本作为服务器,台式机作为客户机. 这样使用了大概一年多,但 ...

  5. iOS app集成支付宝支付流程及后台php订单签名处理

    iOS app集成支付宝支付流程 1: 开通支付宝商户 由公司去支付宝 https://b.alipay.com/order/serviceIndex.htm 签约支付宝开通支付宝商家: 2:商户支付 ...

  6. 3DMax脚本插件--改动材质&amp;贴图名称

    从网上淘到了一套人物的模型,当时的心情是激动无比,掏出用的不熟练的3DMax折腾了半天.突然发现了一个蛋疼的事儿,所有的模型文件,材质名称,子材质,以及贴图所实用的是中文命名!! ! 尽管说是能跑,只 ...

  7. 通过Shell命令与JavaAPI读取ElasticSearch数据 (能力工场小马哥)

    主要内容: 通过JavaAPI和Shell命令两种方式操作ES集群 集群环境: 两个 1,未配置集群名称的单节点(模拟学习测试环境); 2,两个节点的集群(模拟正常生产环境). JDK8+Elasti ...

  8. 使用Python创建MySQL数据库实现字段动态添加以及动态的插入数据

    应用场景: 我们须要设计一个数据库来保存多个文档中每一个文档的keyword. 假如我们每一个文档字符都超过了1000,取当中出现频率最大的为我们的keyword. 如果每个文档的keyword都超过 ...

  9. Java 基础【13】 I/O流概念分析整理

    转载地址:http://blog.csdn.net/yuebinghaoyuan/article/details/7388059 java.io 中的流,可以从不同的角度进行分类. 按照数据流的方向不 ...

  10. SIT与UAT的分别

    在企业级软件的测试过程中,经常会划分为三个阶段——单元测试,SIT和UAT,如果开发人员足够,通常还会在SIT之前引入代码审查机制(Code Review)来保证软件符合客户需求且流程正确.下面简单介 ...