React + nodemailer + koa-jwt 实现登录注册邮箱验证
完整源码在github,下载server和react部分
最近在做一个JSPatch后台管理系统。起初只是自己内部用,后来买了阿里云的免费服务器,用docker+jenkins完成了一个自动化部署,于是就想把这个小东西放到公网里。所以现在开始回来完善一下登录注册的功能,当然这个不是最终解决方案,只是帮助大家了解Json Web Token的基本流程和koa-jwt的基本用法。
一.Json Web Token介绍
由于http协议的无状态性,有些时候我们需要保存一些状态,比如用户的登录信息等。目前主要用到的一种方式是session + cookie。这种方式我之前也有过实现,但是只适用于浏览器端而不适用于原生应用。另外一种方式就是我们今天要讲到的Json Web Token。
1.组成
JWT主要由三部分组成
Header:base64编码的json object,包含token类型和使用的加密算法。一个Header行如下
{"alg": "HS256", "typ": "JWT"}
Payload:base64编码的json object,包含一些自定义信息(用户唯一标识),和一些jwt预留的标识。常用的有iss(签发者),exp(过期时间戳),sub(面向的用户),aud(接收方),iat(签发时间)等。jwt不会强制要求你使用预留标识,一个简单Payload行如下。
{"uid": 42245, "exp": Date.now()+10*60*1000}
Signature: 根据Header,PayloadA 和一个密钥(只有服务端知道),并利用Header中指定的加密算法,生成的一个签名串。
2.输出形式
JWT格式的输出是以.分隔的三段Base64编码,与SAML等基于XML的标准相比,JWT在HTTP和HTML环境中更容易传递。一个标准的JWT输出大概是这样子的
3.鉴权流程
这里以koa-jwt为例。服务端调用koa-jwt的sign方法生成token,通过api请求下发到客户端。客户端存入本地(h5就是localstorage,cookie,sessionstorage等,iOS的话就是NSUserDefault)。客户端在下一次请求的时候在header里面带上token,服务端koa-jwt检测到token,先进行校验,校验成功以后用decode方法解密,并直接赋予this.state.user。以下是一个流程图。
二.具体使用
看了上面的介绍,大家也许还一头雾水?没关系,下面我们在实战中具体来操作。先来看一下我们具体要实现的功能。首先我们需要有一个注册的页面
注册完成以后,我们需要验证用户的邮箱
验证完成以后,跳转到主页面
最后我们还有一个登录页面,如果登录之后没验证邮箱的,也跳转到认证邮箱页面,否则跳转到主页面
前端大概就是这么几个页面,很简单,大家可以用React把这几个页面画出来,然后用React Router完成相关路由设定。页面部分的代码我就不在贴出,待源码整理完成以后会提供大家参考,本文的重点是JWT。
下面看一下server部分的,除了之前讲解koa上传图片用到的基本库以外,本文还需要用到koa-jwt和nodemailer两个库,首先用npm install xxx --save安装jwt和nodemailer。
先来看一下我的一个整体的项目结构
项目的路由由于引入koa-frouter,所以是根据文件路径来决定的。我设置的是routers下面的文件。model用于生成mongodb的Schema,lib下主要是提供一些mongo的增删改查函数。
然后,打开app.js
我们引入koa-jwt并且加入到中间件级联中。这里的secret就是我们之前所讲的JWT用来生成Signature的密钥。由于我们的登录,注册这几个路由不需要用到JWT验证,所以我们把相关的几个路由忽略掉。
首先我们先来写一下注册的接口,在routers文件夹下创建register.js,然后在model下创建User.js,我们先来生成一个UserSchma。
这里主要看activeKey和isActive。activeKey我这里是用邮箱的md5(也可以用别的随机字符串,保证唯一性),isActive是表明用户验证过没有。我这里认证邮箱的思路大概是这样:首先用户注册,注册成功后把用户邮箱md5以后存到activeKey里面(email是不能重复的),用koa-jwt生成token,然后用nodemailer往注册邮箱发一封邮件,邮件包含一个链接。链接打开后会访问验证邮箱页面,并且。把activeKey放到链接的查询参数里面(query),在认证邮箱页面请求服务端认证接口,如果参数key和数据库里面保存的key相同,就说明认证成功,把数据库里的isActive改为true。
在lib目录下,创建User.js
这里就简单封装了我们要用到的几个函数。然后在routers下创建register.js。
引入lib下的User.js,nodemailer和koa-jwt。在这里我用到了koa-scheme这个库,所以对post请求上来的body数据合法性检测就放到了config下的config.schema.js里面。主要是邮箱合法性,以及生成activeKey的一些操作。在register.js里面我们就无需再写这些繁琐的数据校验。在register的post函数中,我们首先检验邮箱是否已存在,若不存在,入库,然后生成token。这里我们用koa-jwt提供的sign。
在之前我们有讲过JWT包含三部分。Header一般情况我们并不需要指定。我们给koa-jwt的sign方法传递了三个参数:
第一个是Payload,也就是用户信息(要注意payload不要传整个文档,Payload需要的是唯一且不变的数据,否则当Payload改变的时候需要重新下发token)。这里我们用文档的id,目的是唯一标识用户,并且不可修改但是它默认是ObjectId类型的,我们使用toString方法让其转化为字符串。
第二个参数是密钥,也就是你生成Signature时所用到的加密密钥。要注意这里必须和创建jwt的时候传入的secret一致,因为服务端需要用创建时的secret来解密。
第三个参数则是设置一个token的过期时间,这里我们设置的是1天。
这样我们就生成了一个token,这个token我们下发到浏览器端,然后存到localstorage里面。
然后我们使用nodemailer给指定邮箱发邮件。这里我们默认发送到我的一个网易邮箱。
注意mailOptions里面的html,这里的html就是在邮件内容里面显示出来的html。这里我们用一个a标签包一个超链接,链接到前端验证邮箱的页面,并带上查询参数verifyKey。收到邮件后,根据React Router点击会跳转到VerifyEmail组件
在组件挂载完毕之后,我们从loaction的query里取verifyKey参数,如果存在,证明是点击邮箱跳转过来的,这样我们就调用一下网络请求的函数访问服务端verifyemail接口。
网络请求的函数我写进了Component的扩展里面,方便调用。Category里面,我们对10002和10001错误进行了统一处理。一个是说明用户未验证邮箱,一个则是说明用户未登录。
另外,在network里面我用了这种返回Promise的方法,也是避免回调函数嵌套。注意network里面,我们会从localstorage里面取一次token。如果token存在,证明服务端授权过。所以我们在请求的header里面增加Authorization字段,并且用Bearer + 空格 + token的形式把服务端生成并返回给我们的token传入。
当你的请求头里包含Authorization,并且koa-jwt没有unless的情况下,koa-jwt就会对这一次API访问进行鉴权。如果成功,会把token解密后的数据放到ctx.state.user中,在koa里我们的上下文就是this。当然这个user的名称是可以更换的,具体的请在github查看koa-jwt的usage。
现在由于我们是点击邮箱跳转过来的,所以verifyKey参数也是有的,再加上之前注册服务端返回并存到localstorage的token,所以我们在VerifyEmail里面直接请求verifyemail接口。在服务端routers文件夹下我们创建verifyemail.js
这个逻辑就很简单了,拿到前端传过来的verifyKey,再拿到jwt解密出来的文档id,然后查询出来id所对应的doc,判断两个key是否相等。相等的话就说明认证成功,然后更新文档的isActive字段,并返回成功给前端。前端判断如果code为0的话,就直接跳转到主页。
到这里我们已经完成了一个简短的邮箱验证,但是还有一些细节需要处理。
接下来我们看一下登录,在routers下创建login.js。
和注册的逻辑基本一样,只不过我们需要验证一下密码。在这里存在的一个逻辑就是,如果注册完没有立即去验证邮箱,也是能登录进来的。在这种情况下,我们需要在登录成功之后跳转到认证邮箱的页面。在这里我们实现的逻辑是登录成功后默认跳转到主页,主页会调用getapplist接口。然后我们对所有请求添加一个中间件进行处理。打开app.js。
添加如上代码,这里添加一个中间件。除login,register,verifyemail三个接口之外,所有接口都会验证User文档的isActive是否为true,若为false,则直接返回code==10002。若不为则交给下面的中间件继续处理。
前端的话,就要对所有网络请求进行统一的处理,这也就是我们为什么要封装网络请求的原因。(请看上文的Category.js)
所以当我们登录并未验证的用户,getapplist请求会被该中间件拦截并返回code==10002。前端对返回code==10002会跳转到认证邮箱页。同理未登录的用户会返回code==10001。
三.总结
关于JWT Token过期问题
1.如果在payload里面设置了exp属性,则你可以将你的token存到localstorage里面,当到达过期时间以后,你再用这个token去请求API,服务器会抛出Thrown error if the token is expired错误。
Thrown error if the token is expired.
Error object:
- name: 'TokenExpiredError'
- message: 'jwt expired'
- expiredAt: [ExpDate]
2.对于没有设置exp的童鞋,你可以存到cookie里面并设置过期时间。或者在localstorage里面再增加一条创建时间的字段。两种方式都可以看个人的喜好。
The End
至此我们已经用React+nodemailer+koa-jwt实现了一个简单的登录注册及邮箱验证功能。如果你正在为原声移动应用或者SPA编写API的话,JWT会是一个非常不错的选择。有一点需要记住的是: 在浏览器中使用JWT你需要将它存在LocalStorage或者SessionStorage中,但这可能会导致XSS攻击。关于XSS攻击的话,网上有详细的文章来说明,这里就不赘述。
源码待我整理完毕后会上传到github。
好了,打个小广告。小弟和几个朋友一起弄一个技术公众号,主要专注于React全栈相关技术。感兴趣的朋友可以关注,一起交流学习。公众号另外一位小编可是资深React和Node开发者哦!
如果你也喜欢全栈,请关注React全栈开发吧!!
React + nodemailer + koa-jwt 实现登录注册邮箱验证相关推荐
- 原生JavaScript实现登录注册前端验证
原生JavaScript实现登录注册前端验证 功能:实现一个简单的前端登录页面的验证, 废话不多说,直接上代码... 登录窗口代码段... //html <div class="log ...
- Destoon如何去除登录的邮箱验证?
Destoon如何去除登录的邮箱验证? 主要修改下面的函数即可,/module/member/member.class.php function is_member($member) {global ...
- JavaMail实现注册邮箱验证案例
原文链接:https://www.jianshu.com/p/8f8d7a46888f 在日常生活中,我们在一个网站中注册一个账户时,往往在提交个人信息后,网站还要我们通过手机或邮件来验证,邮件的话大 ...
- 干货!flask登录注册token验证接口开发详解
今天给大家献上登录注册接口开发,是基于token验证的.咱们闲言少叙,进入正题! 首先看一下数据库模型: #pip install passlib from passlib.apps import c ...
- 基于jwt的登录/注册功能实现
# views.py # 登录视图 class LoginView(ViewSet):# 登录@action(methods=['POST'], detail=False)def login(self ...
- jwt判断token是否过期_4spring-security5整合jwt做登录、权限验证,全网最全!!!可用...
github源码: https://github.com/gyb123456/spring-security5-jwt,最烦那些写文档只截图一半还不给源码的人,要不你就截全图,要不就给源码! 前言: ...
- h5 php登录注册页面验证,H5制作一个注册页面的代码实例
HTML5写的注册页面,正在学习html5的朋友可以参考下 代码如下: register.html function play(){ document .getElementById("me ...
- phpcmsV9 邮箱注册:邮箱验证(不改代码、含演示截图) - 配置篇
phpcmsV9 邮箱注册:邮箱验证(不改代码.含演示截图) - 全程指导 方法一.(网传) · 配置教程 第一步:修改登陆的验证JS 第二步: 修改登录文件 方法二.真机实操 · 教程 [推荐] 1 ...
- 注册验证之邮箱验证(SpringBoot框架)
一.首先先加入邮箱依赖,看下面链接! JAVA实现邮件发送(SPRING BOOT 框架) 二.大概思路及实现 1.数据库加一个验证字段 2.先让用户进行注册 前端实现:(正常的注册,只是邮箱必填,会 ...
最新文章
- 如何在内核里面查找某些结构体或者宏的定义
- bzoj 1058: [ZJOI2007]报表统计
- 面试官:重写 equals 时为什么一定要重写 hashCode?
- vSAN6.2 性能服务
- PTA L2-006 树的遍历-二叉树的后序遍历+中序遍历,输出层序遍历 团体程序设计天梯赛-练习集...
- SK海力士与电装四巨头论半导体供给
- 负载均衡获得真实源IP的6种方法 【转】
- 条件判断_判断疑似陨石应具备什么条件下,才能判断陨石真伪
- 初始化问题(其中含有盲区,{}和()的区别)
- 【利用WPS功能破解密码】笔记
- Web渗透测试-实战 方法 思路 总结
- qcap 教程_高通平台抓取ramdump及使用qcap解析,ramdumpqcap
- Spring boot应用【tailf】服务启动停止管理脚本
- 一些CS领域、互联网领域的名词解释,作为知识补充
- HTML菜单中有关selected=true和setAttribute(“selected“,“selected“)的异同以及selected设置无法生效的问题解析
- C++ for循环效率优化
- 幂等和非幂等的关系与区别
- Booth乘法器设计
- 反编译工具Virtuous Ten Studio使用
- 拓扑排序与关键路径(AOV网和AOE网)
热门文章
- 杭州阿里总部五面全过程:一面技术面+二面(项目+技术)+三面(项目经理面)+四面(地区技术负责人面)
- 多线程十二种设计模式详解
- Dev-c++的小游戏代码(可直接复制)
- 半导体材料的霍尔效应测试简介
- 天祥电子proteus原理图c51+avr+pic
- 手机话筒破音测试软件,如何解决手机话筒有杂音的问题?
- springBoot配置文件密码加密
- IMU(惯性测量单元)学习
- matlab 求样本离差阵,样本的离差、标准差、方差、偏度、(多图)
- Python爬虫出现cannot use a string pattern on a bytes-like object