React + nodemailer + koa-jwt 实现登录注册邮箱验证

前端之家收集整理的这篇文章主要介绍了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输出大概是这样子的


JWT输出示例

3.鉴权流程

这里以koa-jwt为例。服务端调用koa-jwt的sign方法生成token,通过api请求下发到客户端。客户端存入本地(h5就是localstorage,cookie,sessionstorage等,iOS的话就是NSUserDefault)。客户端在下一次请求的时候在header里面带上token,服务端koa-jwt检测到token,先进行校验,校验成功以后用decode方法解密,并直接赋予this.state.user。以下是一个流程图。

JWT流程

二.具体使用

看了上面的介绍,大家也许还一头雾水?没关系,下面我们在实战中具体来操作。先来看一下我们具体要实现的功能。首先我们需要有一个注册页面

注册

注册完成以后,我们需要验证用户的邮箱

验证邮箱页

验证完成以后,跳转到主页面

主页

最后我们还有一个登录页面,如果登录之后没验证邮箱的,也跳转到认证邮箱页面,否则跳转到主页面

前端大概就是这么几个页面,很简单,大家可以用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

app.js

我们引入koa-jwt并且加入到中间件级联中。这里的secret就是我们之前所讲的JWT用来生成Signature的密钥。由于我们的登录注册这几个路由不需要用到JWT验证,所以我们把相关的几个路由忽略掉。

首先我们先来写一下注册的接口,在routers文件夹下创建register.js,然后在model下创建User.js,我们先来生成一个UserSchma。

User.js

这里主要看activeKey和isActive。activeKey我这里是用邮箱的md5(也可以用别的随机字符串,保证唯一性),isActive是表明用户验证过没有。我这里认证邮箱的思路大概是这样:首先用户注册注册成功后把用户邮箱md5以后存到activeKey里面(email是不能重复的),用koa-jwt生成token,然后用nodemailer往注册邮箱发一封邮件邮件包含一个链接链接打开后会访问验证邮箱页面,并且。把activeKey放到链接查询参数里面(query),在认证邮箱页面请求服务端认证接口,如果参数key和数据库里面保存的key相同,就说明认证成功,把数据库里的isActive改为true。

在lib目录下,创建User.js

这里就简单封装了我们要用到的几个函数。然后在routers下创建register.js。

config.scheme.js
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里面。

前端Register.js

然后我们使用nodemailer给指定邮箱发邮件。这里我们默认发送到我的一个网易邮箱。

注意mailOptions里面的html,这里的html就是在邮件内容里面显示出来的html。这里我们用一个a标签包一个超链接链接到前端验证邮箱的页面,并带上查询参数verifyKey。收到邮件后,根据React Router点击会跳转到VerifyEmail组件

前端VerifyEmail.js

在组件挂载完毕之后,我们从loaction的query里取verifyKey参数,如果存在,证明是点击邮箱跳转过来的,这样我们就调用一下网络请求的函数访问服务端verifyemail接口。

前端Category.js
前端network.js

网络请求的函数我写进了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

verifyemail.js

这个逻辑就很简单了,拿到前端传过来的verifyKey,再拿到jwt解密出来的文档id,然后查询出来id所对应的doc,判断两个key是否相等。相等的话就说明认证成功,然后更新文档的isActive字段,并返回成功给前端。前端判断如果code为0的话,就直接跳转到主页。

到这里我们已经完成了一个简短的邮箱验证,但是还有一些细节需要处理。

接下来我们看一下登录,在routers下创建login.js。

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相关文章