视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
node.js+captchapng+jsonwebtoken实现登录验证示例
2020-11-27 22:32:25 责编:小OO
文档

提到登录验证,大家肯定能想到的就是12306的验证码了吧。12306为了防止刷票,可以说是煞费苦心,验证码也越来越难识别,最终即使是人也可能识别不了了。

今天,小编就给大家说一下node如何实现图片验证码,以及使用token验证登录。学习本文你将学到:

1.使用captchapng生成图片验证码

2.使用jsonwebtoken实现登录验证

一、图片验证码生成(最后有全部代码)

首先,我们理一下流程,第一步服务器要随机生成一组四位数。

第二步,将这四位数用canvas绘图生成图片。

第三步,我们要将这四位数存起来,以便用户返回数据时做对比。

那么存到哪里呢?很明显为了区分用户,存到session最为稳妥。

第一步,先要有一个登录页面。在这里我们依然用react,

login.tsx

import * as React from 'react'
import * as ReactDom from 'react-dom'
import {Link, browserHistory} from 'react-router';
import * as axios from 'axios';
export default class Login extends React.Component<any,any>{
 constructor(props){
 super(props)
 this.state = {
 userName : '',
 password : '',
 yzNoId : '',
 hash : Math.random()
 }
 }
 handleUserName(e) : any {
 this.setState({
 userName : e.target.value
 })
 }
 handlePassword(e) : any {
 this.setState({
 password : e.target.value
 })
 }
 handleYzId(e) : any {
 this.setState({
 yzNoId : e.target.value
 })
 }
 render(){
 const { userName, password, yzNoId } = this.state;
 return(
 <div>
 <div className="nav-wrap">
 <ul className="nav">
 <li><Link to="/home">首页</Link></li>
 <li><Link to="/imgLoad">上传</Link></li>
 <li><Link to="/login">登陆</Link></li>
 </ul>
 </div>
 <div className="content">
 <div className="login-warp">
 <p>
 <input type="text" className="username" value={userName} onChange={this.handleUserName.bind(this)} placeholder="用户名"/>
 </p>
 <p>
 <input type="text" className="password" value={password} onChange={this.handlePassword.bind(this)} placeholder="密码"/> 
 </p> 
 <p>
 <input type="text" className="yz" value={yzNoId} onChange={this.handleYzId.bind(this)} placeholder="验证码"/>
 <img src={"http://localhost:3000/captcha"} className="yz-img" /> 
 </p>
 <p>
 <input type="button" className="submit" value="登陆" onClick={this.sbumit.bind(this,{userName:userName,password:password,captcha:yzNoId})} /> 
 </p> 
 </div>
 </div>
 </div>
 )
 }
}

页面是这样的


我们需要通过服务器给一张验证图片。

router/index.js 添加如下代码

var Login = require('./controller/login');
var login = new Login;
router.get('/captcha', login.captcha);
router.post('/login',login.loginer);
login是定义在控制器的一个类的实例,captcha,loginer是它的方法。分别是返回验证图片、登录验证。
controller/login.js

var rf = require('fs');
var captchapng = require('captchapng');
class Login {
 constructor(){}
 captcha(req, res, next) {
 var str = parseInt(Math.random()*9000+1000); //随机生成数字
 req.session.captcha = str; // 存入session
 var p = new captchapng(80, 30, str); //生成图片
 p.color(0, 0, 0, 0);
 p.color(80, 80, 80, 255);
 var img = p.getBase();
 var imgbase = new Buffer(img, 'base');
 res.writeHead(200, {
 'Content-Type': 'image/png'
 });
 res.end(imgbase);
 }
 loginer(req, res, next) {
 let captcha = req.body.captcha;
 let userName = req.body.userName;
 let password = req.body.password;
 if (captcha != req.session.captcha) {
 res.status(400).send({
 message: '验证码错误'
 });
 }else if(userName == "chenxuehui" && password == "123321"){
 res.json({"code":100,"verson":true,"msg":"登陆成功","token":token});
 }else{
 res.json({"code":0,"verson":false,"msg":"密码错误"});
 }
 }
}
module.exports = Login

captcha方法是生成一张含四位数字的图片,然后将图片保存到session中。

将此方法在 router/index.js 中引用

router.get('/captcha', login.captcha);

也就是说我们访问localhost:3000/captcha就会返回一张图片。

有了这个连接后我们就可以通过图片的src属性拿到该图片,但是当点击图片的时候要重新刷新,所以我们需要给图片添加一个点击刷新事件。将下面代码插入到login.tsx中

setHash() {
 this.setState({
 hash : Math.random()
 })
}

img标签也变成了

代码如下:
<img src={"http://localhost:3000/captcha?aaa="+this.state.hash}  className="yz-img" onClick={this.setHash.bind(this)} />  
 

此时login.tsx全部代码:

import * as React from 'react'
import * as ReactDom from 'react-dom'
import {Link, browserHistory} from 'react-router';
import * as axios from 'axios';
export default class Login extends React.Component<any,any>{
 constructor(props){
 super(props)
 this.state = {
 userName : '',
 password : '',
 yzNoId : '',
 hash : Math.random()
 }
 }
 public async sbumit(params : any) : Promise<any>{
 let res = await axios.post('http://localhost:3000/login',params);
 }
 handleUserName(e) : any {
 this.setState({
 userName : e.target.value
 })
 }
 handlePassword(e) : any {
 this.setState({
 password : e.target.value
 })
 }
 handleYzId(e) : any {
 this.setState({
 yzNoId : e.target.value
 })
 }
 setHash() {
 this.setState({
 hash : Math.random()
 })
 }
 render(){
 const { userName, password, yzNoId } = this.state;
 return(
 <div>
 <div className="nav-wrap">
 <ul className="nav">
 <li><Link to="/home">首页</Link></li>
 <li><Link to="/imgLoad">上传</Link></li>
 <li><Link to="/login">登陆</Link></li>
 </ul>
 </div>
 <div className="content">
 <div className="login-warp">
 <p>
 <input type="text" className="username" value={userName} onChange={this.handleUserName.bind(this)} placeholder="用户名"/>
 </p>
 <p>
 <input type="text" className="password" value={password} onChange={this.handlePassword.bind(this)} placeholder="密码"/> 
 </p> 
 <p>
 <input type="text" className="yz" value={yzNoId} onChange={this.handleYzId.bind(this)} placeholder="验证码"/>
 <img src={"http://localhost:3000/captcha?aaa="+this.state.hash} className="yz-img" onClick={this.setHash.bind(this)} /> 
 </p>
 <p>
 <input type="button" className="submit" value="登陆" onClick={this.sbumit.bind(this,{userName:userName,password:password,captcha:yzNoId})} /> 
 </p> 
 </div>
 </div>
 </div>
 )
 }
}

这样只要点击img,就会随机生成一个hash,然后就会调用新的图片出来。

接着我们进行登录验证。

loginer方法就是进行登录验证的。

拿到用户的用户名信息,密码以及验证码一次对比,最后返回登录是否成功数据。

当用户登陆成功以后,下次登录就不需要再次登录了,以往的方法可以选则session或者cookie的方式,在这里我们使用token。因为现在已经实现了前后端分离开发,我们更倾向于构建单页面配合ajax构建应用。而token最适合这种开发模式不过了。

token登录验证

token是一串经过加密的字符串,登录成功以后返回给用户保存,然后用户在请求接口时,都带这个token。所以我们需要对token进行加密。

Json Web Token就是专门解决这个问题的,原理就不做详解了,其实就是按照一定的方式得到一个字符串,然后在通过某种方式解开。

我们要做的第一步就是

当用户登录成功后,创建一个token返回给用户。

第二步:用户拿到token后应该把token存到本地。

第三步:需要写一个中间层,每次用户请求时我们验证用户携带的token是否正确。正确返回数据,不正确返回警告。
用户每次请求数据的时候要在header里把token带上。

第一步:还是controller/login.js

var rf = require('fs');
var jwt = require('jsonwebtoken');
var captchapng = require('captchapng');
var Tokens = require('../middleware/token')
var t = new Tokens;
class Login {
 constructor(){}
 captcha(req, res, next) {
 var str = parseInt(Math.random()*9000+1000); //随机生成数字
 req.session.captcha = str; // 存入session
 var p = new captchapng(80, 30, str); //生成图片
 p.color(0, 0, 0, 0);
 p.color(80, 80, 80, 255);
 var img = p.getBase();
 var imgbase = new Buffer(img, 'base');
 res.writeHead(200, {
 'Content-Type': 'image/png'
 });
 res.end(imgbase);
 }
 loginer(req, res, next) {
 let captcha = req.body.captcha;
 let userName = req.body.userName;
 let password = req.body.password;
 if (captcha != req.session.captcha) {
 res.status(400).send({
 message: '验证码错误'
 });
 }else if(userName == "chenxuehui" && password == "123321"){
 // 设置token
 var datas = {userName:"chenxuehui"}
 //调用../middleware/token 下方法设置
 var token = t.setToken('cxh',300,datas)
 res.json({"code":100,"verson":true,"msg":"登陆成功","token":token});
 }else{
 res.json({"code":0,"verson":false,"msg":"密码错误"});
 }
 }
}

module.exports = Login

这次在loginer方法里面我们加入设置token,并返回给用户。setToken方法是设置token的方法。

第二步:用户拿到后保存。

在login.tsx就变成如下

import * as React from 'react'
import * as ReactDom from 'react-dom'
import {Link, browserHistory} from 'react-router';
import * as axios from 'axios';
export default class Login extends React.Component<any,any>{
 constructor(props){
 super(props)
 this.state = {
 userName : '',
 password : '',
 yzNoId : '',
 hash : Math.random()
 }
 }
 public async sbumit(params : any) : Promise<any>{
 let res = await axios.post('http://localhost:3000/login',params);
 if(res.data.verson){
 sessionStorage.setItem('token',res.data.token);
 browserHistory.push("/home")
 }
 }
 handleUserName(e) : any {
 this.setState({
 userName : e.target.value
 })
 }
 handlePassword(e) : any {
 this.setState({
 password : e.target.value
 })
 }
 handleYzId(e) : any {
 this.setState({
 yzNoId : e.target.value
 })
 }
 setHash() {
 this.setState({
 hash : Math.random()
 })
 }
 render(){
 const { userName, password, yzNoId } = this.state;
 return(
 <div>
 <div className="nav-wrap">
 <ul className="nav">
 <li><Link to="/home">首页</Link></li>
 <li><Link to="/imgLoad">上传</Link></li>
 <li><Link to="/login">登陆</Link></li>
 </ul>
 </div>
 <div className="content">
 <div className="login-warp">
 <p>
 <input type="text" className="username" value={userName} onChange={this.handleUserName.bind(this)} placeholder="用户名"/>
 </p>
 <p>
 <input type="text" className="password" value={password} onChange={this.handlePassword.bind(this)} placeholder="密码"/> 
 </p> 
 <p>
 <input type="text" className="yz" value={yzNoId} onChange={this.handleYzId.bind(this)} placeholder="验证码"/>
 <img src={"http://localhost:3000/captcha?aaa="+this.state.hash} className="yz-img" onClick={this.setHash.bind(this)} /> 
 </p>
 <p>
 <input type="button" className="submit" value="登陆" onClick={this.sbumit.bind(this,{userName:userName,password:password,captcha:yzNoId})} /> 
 </p> 
 </div>
 </div>
 </div>
 )
 }
}

在sbumit方法里我们将token放到了sessonstorage里面。

第三步:设置中间件每次请求接口时,验证token,如果解析成功加入到请求头里面。

./middleware/token.js

var jwt = require('jsonwebtoken');
class Tokens {
 constructor(){}
 testToken(req,res,next) {
 var token = req.body.token || req.query.token || req.headers['x-access-token'];
 
 if(token) {
 //存在token,解析token
 jwt.verify(token, 'cxh' , function(err,decoded) {
 if(err) {
 // 解析失败直接返回失败警告
 return res.json({success:false,msg:'token错误'})
 }else {
 //解析成功加入请求信息,继续调用后面方法
 req.userInfo = decoded;
 next()
 }
 })
 }else {
 return res.status(403).send({success:false,msg:"没有token"})
 }
 }
 setToken(name,time,data) {
 var jwtSecret = name;
 var token = jwt.sign(data, jwtSecret, {
 expiresIn: time
 })
 return token;
 }
}
module.exports = Tokens

testToken方法是验证token,setToken是设置token方法

假如没有登录请求是这样的


在 router/index.js

var express = require('express');
var router = express.Router();
var rf = require('fs');
var Login = require('./controller/login');
var Tokens = require('./middleware/token')
var t = new Tokens;
var login = new Login;
//主页
router.get('/', function(req, res, next) {
 res.render("wap/index")
});
//获取图片验证码
router.get('/captcha', login.captcha);
//登录验证
router.post('/login',login.loginer);
//请求数据时 t.testToken 验证token
router.post('/list',t.testToken,function(req, res, next){
 res.json({
 //在请求信息里面拿到数据
 username : req.userInfo.userName,
 success : true,
 result : [
 {
 name:'1111111'
 },
 {
 name :'22222'
 }
 ]
 })
})
module.exports = router;

我们在另一个页面调用list接口试一下

import * as axios from 'axios';
import { transToken } from '../decorator/index'


class Home extends React.Component<any,any>{
 constructor(props){
 super(props)
 this.state = {
 data : ''
 }
 }
 async getList(): Promise<any>{
 let token = sessionStorage.getItem('token');
 const config = {
 // 请求头信息
 headers: {'x-access-token': token} 
 }
 let res = await axios.post('http://localhost:3000/list',{},config);
 if(!res.data.success){
 browserHistory.push('/login');
 return;
 }
 this.setState({
 data : res.data
 })
 }
 render(){
 const { data } = this.state;
 return(
 <div>
 <div className="nav-wrap">
 <ul className="nav">
 <li><Link to="/home">首页</Link></li>
 <li><Link to="/imgLoad">上传</Link></li>
 <li><Link to="/login">登陆</Link></li>
 </ul>
 </div>
 <div className="content">
 Home
 <span onClick={this.getList.bind(this)}>获取数据</span>
 <div>{
 data ? data.result.map( (val,k) => {
 return <li key = {k}>{val.name}</li>
 }) : null
 }</div>
 </div>
 </div>
 
 )
 }
}
export default Home

当调用getList时,如果此时没有登录res.data.success就会为false,则跳到登录页。

全部代码

node.js

app.js

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require("express-session");
var ejs = require('ejs');

var index = require('./routes/index');


var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
// app.set('view engine', 'jade');
app.engine('html', ejs.__express);
app.set('view engine', 'html');
app.use(session({
 secret:"dabao",
 resave:false,
 saveUninitialized:true,
 cookie:{}
}));
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser({limit: 5000000}));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, '')));

app.use('/', index);


// catch 404 and forward to error handler
app.use(function(req, res, next) {
 var err = new Error('Not Found');
 err.status = 404;
 next(err);
});

// error handler
app.use(function(err, req, res, next) {
 // set locals, only providing error in development
 res.locals.message = err.message;
 res.locals.error = req.app.get('env') === 'development' ? err : {};

 // render the error page
 res.status(err.status || 500);
 res.render('error');
});

module.exports = app;

index.js

var express = require('express');
var router = express.Router();
var rf = require('fs');
var Login = require('./controller/login');
var Tokens = require('./middleware/token')
var t = new Tokens;
var login = new Login;
/* GET home page. */
router.get('/', function(req, res, next) {
 res.render("wap/index")
});
router.post('/upLoadImg',function(req,res,next){
 let imgData = req.body.imgData;
 console.log(imgData)
 let baseData = imgData.replace(/^data:image\/\w+;base,/, "");
 let dataBuffer = new Buffer(baseData, 'base');
 let timer = Number( new Date() );
 console.log(timer)
 rf.writeFile("views/images/artCover"+timer+".png",dataBuffer, function(err) {
 if(err) {
 res.json({"code":400,"verson":false,"msg":err});
 }else {
 res.json({"code":100,"verson":true,"url":"views/src/common/images/artCover/"+timer+".png"});
 }
 });
})
router.get('/captcha', login.captcha);
router.post('/login',login.loginer);
router.post('/list',t.testToken,function(req, res, next){
 // 先解析token 
 console.log(req.userInfo)
 res.json({
 username : req.userInfo.userName,
 success : true,
 result : [
 {
 name:'1111111'
 },
 {
 name :'22222'
 }
 ]
 })
})
module.exports = router;

controller/login.js

var rf = require('fs');
var jwt = require('jsonwebtoken');
var captchapng = require('captchapng');
var Tokens = require('../middleware/token')
var t = new Tokens;
class Login {
 constructor(){}
 captcha(req, res, next) {
 var str = parseInt(Math.random()*9000+1000); //随机生成数字
 req.session.captcha = str; // 存入session
 var p = new captchapng(80, 30, str); //生成图片
 p.color(0, 0, 0, 0);
 p.color(80, 80, 80, 255);
 var img = p.getBase();
 var imgbase = new Buffer(img, 'base');
 res.writeHead(200, {
 'Content-Type': 'image/png'
 });
 res.end(imgbase);
 }
 loginer(req, res, next) {
 let captcha = req.body.captcha;
 let userName = req.body.userName;
 let password = req.body.password;
 if (captcha != req.session.captcha) {
 res.status(400).send({
 message: '验证码错误'
 });
 }else if(userName == "chenxuehui" && password == "123321"){
 // 设置token
 var datas = {userName:"chenxuehui"}
 var token = t.setToken('cxh',300,datas)
 res.json({"code":100,"verson":true,"msg":"登陆成功","token":token});
 }else{
 res.json({"code":0,"verson":false,"msg":"密码错误"});
 }
 }
}
module.exports = Login

middleware/token.js

var jwt = require('jsonwebtoken');

class Tokens {
 constructor(){}
 testToken(req,res,next) {
 var token = req.body.token || req.query.token || req.headers['x-access-token'];

 if(token) {
 jwt.verify(token, 'cxh' , function(err,decoded) {
 if(err) {
 return res.json({success:false,msg:'token错误'})
 }else {
 req.userInfo = decoded;
 next()
 }
 })
 }else {
 return res.status(403).send({success:false,msg:"没有token"})
 }
 }
 setToken(name,time,data) {
 var jwtSecret = name;
 var token = jwt.sign(data, jwtSecret, {
 expiresIn: time
 })
 return token;
 }
}
module.exports = Tokens

react部分

login.tsx

import * as React from 'react'
import * as ReactDom from 'react-dom'
import {Link, browserHistory} from 'react-router';
import * as axios from 'axios';
export default class Login extends React.Component<any,any>{
 constructor(props){
 super(props)
 this.state = {
 userName : '',
 password : '',
 yzNoId : '',
 hash : Math.random()
 }
 }
 public async sbumit(params : any) : Promise<any>{
 let res = await axios.post('http://localhost:3000/login',params);
 if(res.data.verson){
 sessionStorage.setItem('token',res.data.token);
 browserHistory.push("/home")
 }
 }
 handleUserName(e) : any {
 this.setState({
 userName : e.target.value
 })
 }
 handlePassword(e) : any {
 this.setState({
 password : e.target.value
 })
 }
 handleYzId(e) : any {
 this.setState({
 yzNoId : e.target.value
 })
 }
 setHash() {
 this.setState({
 hash : Math.random()
 })
 }
 render(){
 const { userName, password, yzNoId } = this.state;
 return(
 <div>
 <div className="nav-wrap">
 <ul className="nav">
 <li><Link to="/home">首页</Link></li>
 <li><Link to="/imgLoad">上传</Link></li>
 <li><Link to="/login">登陆</Link></li>
 </ul>
 </div>
 <div className="content">
 <div className="login-warp">
 <p>
 <input type="text" className="username" value={userName} onChange={this.handleUserName.bind(this)} placeholder="用户名"/>
 </p>
 <p>
 <input type="text" className="password" value={password} onChange={this.handlePassword.bind(this)} placeholder="密码"/> 
 </p> 
 <p>
 <input type="text" className="yz" value={yzNoId} onChange={this.handleYzId.bind(this)} placeholder="验证码"/>
 <img src={"http://localhost:3000/captcha?aaa="+this.state.hash} className="yz-img" onClick={this.setHash.bind(this)} /> 
 </p>
 <p>
 <input type="button" className="submit" value="登陆" onClick={this.sbumit.bind(this,{userName:userName,password:password,captcha:yzNoId})} /> 
 </p> 
 </div>
 </div>
 </div>
 )
 }
}

home.js 获取列表信息

import * as React from 'react'
import * as ReactDom from 'react-dom'
import {Link, browserHistory} from 'react-router';
import * as axios from 'axios';
class Home extends React.Component<any,any>{
 constructor(props){
 super(props)
 this.state = {
 data : ''
 }
 }
 async getList(): Promise<any>{
 let token = sessionStorage.getItem('token');
 const config = {
 // 请求头信息
 headers: {'x-access-token': token} 
 }
 let res = await axios.post('http://localhost:3000/list',{},config);
 if(!res.data.success){
 browserHistory.push('/login');
 return;
 }
 this.setState({
 data : res.data
 })
 }
 render(){
 const { data } = this.state;
 return(
 <div>
 <div className="nav-wrap">
 <ul className="nav">
 <li><Link to="/home">首页</Link></li>
 <li><Link to="/imgLoad">上传</Link></li>
 <li><Link to="/login">登陆</Link></li>
 </ul>
 </div>
 <div className="content">
 Home
 <span onClick={this.getList.bind(this)}>获取数据</span>
 <div>{
 data ? data.result.map( (val,k) => {
 return <li key = {k}>{val.name}</li>
 }) : null
 }</div>
 </div>
 </div>
 
 )
 }
}
export default Home

下载本文
显示全文
专题