Koa框架密码和签名验证

bcrypt

数据库中存放的用户密码不能是明文的

bcrypt是单向的,而且经过saltcost的处理,使其受rainbow攻击破解的概率大大降低,同时破解的难度也提升不少,相对于MD5等加密方式更加安全,而且使用也比较简单

  • 下载bcryptnpm i bcrypt --save

  • 修改config/index.js文件,添加配置

// ... const config = Object.assign({}, envConfig, { // ... bcrypt: { rounds: 12 } }) module.exports = config
  • utils/目录添加user.js文件
const bcrypt = require('bcryptjs') const config = require('../config') const bcryptConfig = config.bcrypt // 同步方式加密密码 function encryptPassword(password) { // 生成salt的迭代次数:默认值是10,推荐值12 const saltRounds = (bcryptConfig && bcryptConfig.rounds) || 10 // 随机生成salt const salt = bcrypt.genSaltSync(saltRounds) // 获取hash值 return bcrypt.hashSync(password, salt) } // 异步方式加密密码 async function encryptPasswordAsync(password) { // 生成salt的迭代次数 const saltRounds = (bcryptConfig && bcryptConfig.rounds) || 10 return new Promise((resolve, reject) => { // 生成salt并获取hash值 bcrypt.genSalt(saltRounds, function(err, salt) { err && reject(err) bcrypt.hash(password,salt, function(err, hash) { // 把hash值赋值给password变量 err ? reject(err) :resolve(hash) }) }); }) } // 同步方式验证密码 function verifyPassword(password1, password2){ return bcrypt.compareSync(password1, password2) } // 异步方式验证密码 async function verifyPasswordAsync(password1, password2){ return new Promise((resolve, reject) => { bcrypt.compare(password1, password2, function(err, res) { err ? reject(err) : resolve(res) }) }) } module.exports = { encryptPassword, verifyPassword, encryptPasswordAsync, verifyPasswordAsync }
  • 在代码中使用
const userUtil = require('../utils/user') // 用户页面生成密码,保存到数据库。这里只是模拟,直接输出 router.get('/register', async ctx => { const password = 'admin123456' const res = await userUtil.encryptPasswordAsync(password) ctx.success({ password: res }) }) // 登录页面,验证密码是否正确 router.get('/login', async ctx => { const password = 'admin123456' const password2 = '$2a$12$eIaxaJtMIuH7iJQK.FDwYutY2LYk0OlrcPrTPQbSFVb8uxfIVW/Am' const res = await userUtil.verifyPasswordAsync(password, password2) ctx.success({ password: res }) })

crypto

crypto模块的目的是为了提供通用的加密和哈希算法;提供了加密功能,实现了包括对 OpenSSL 的哈希、HMAC、加密、解密、签名、以及验证功能的一整套封装。

最新版本的node已经内置了crypto模块,不需要独立安装;npm上面的crypto已经不维护了

具体用法参考:Node.js - crypto模块笔记

JWT认证

JWT简介

随着网站前后端分离方案的流行,越来越多的网站从Session Base转为使用Token BaseJWT(Json Web Tokens)作为一个开放的标准被很多网站采用
koa-jwt这个中间件使用JWT认证HTTP请求。

jwt是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准, 主要有三部分组成

  1. Header 标头通常由两部分组成:令牌的类型,即JWT,以及正在使用的签名算法(例如HMAC SHA256RSA
// 这个JSON被编码为Base64Url,形成JWT的第一部分 { "alg": "HS256", "typ": "JWT" }
  1. Payload 令牌的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和其他数据的声明
// 这个JSON被编码为Base64Url,形成JWT的第二部分 { "sub": "1234567890", "name": "John Doe", "admin": true }
  1. Signature 要创建签名部分,您必须采用编码标头,编码的有效负载,秘钥,标头中指定的算法,并对其进行签名
// 以HMAC SHA256算法为例 HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )

koa-jwt

koa-jwt工作流程

  • 用户通过登录Api获取当前用户在有效期内的token
  • 需要身份验证的API则都需要携带此前认证过的token发送至服务端
  • koa2会利用koa-jwt中间件的默认验证方式进行身份验证,中间件会进行验证成功和验证失败的分流。

koa-jwt 主要提供路有权限控制的功能,它会对需要限制的资源请求进行检查

koa-jwt请求头中设置authorizationBearer+ tokenBearer后有空格。koa-jwt也是在该位置获取token

// koa-jwt的默认验证方式: { 'authorization': "Bearer " + token }

app.use(jwt( { secret: 'shared-secret', passthrough:true }))通过添加一个passthrough选项来保证始终传递到下一个(中间件)

app.use(jwt({ secret: 'shared-secret', key: 'jwtdata' }))可以使用另外一ctx key来表示解码数据,然后就可以通过ctx.state.jwtdata代替ctx.state.user获得解码数据

secret 的值可以使用函数代替,以此产生动态的加密秘钥

koa-jwt 依赖于jsonwebtokenkoa-unless两个库的

  • 下载jsonwebtoken koa-jwtnpm i jsonwebtoken koa-jwt --save

  • 修改config/index.js文件,添加token配置

  • 修改utils/user.js文件,添加生成token代码

// 签发、解析`token` const jwt = require('jsonwebtoken') const config = require('../config') // 获取token function getToken(payload = {}) { const tokenConfig = config.token return jwt.sign(payload, tokenConfig.secret, { expiresIn: tokenConfig.expires }) } /** * 验证token - 异步方法 * 成功返回 token解析出来的值 * 失败返回 false */ async function verifyToken(token) { const tokenConfig = config.token return new Promise((resolve, reject) => { try { jwt.verify(token, tokenConfig.secret, function (err, decoded) { err ? resolve(false) : resolve(decoded) }) } catch (e) { resolve(false) } }) } // ... module.exports = { getToken, verifyToken // ... }
  • 在登录的时候,返回token
const userUtil = require('../utils/user') router.get('/login', async ctx => { // ... const token = await userUtil.getToken({ id: userInfo.id, name: userInfo.username, // 设置过期时间(毫秒)为 1 小时 - 这里加这个,可以通过这个时间手动处理超时 originExp: Date.now() + 60 * 60 * 1000, }) ctx.success({ token: token }) })
  • 客户端每次发出请求的时候都带着token,方法是在请求头配置{ 'authorization': "Bearer " + token }

  • 修改app.js文件,配置koa-jwt

// koa-jwt token验证 const koaJwt = require('koa-jwt') const { secret } = require('./config').token app.use(koaJwt({secret}).unless({ // 数组中的路径不需要通过jwt验证 path: [/^\/user\/*/, /\/captcha/,/^\/download/,/^\/uploads/] }))
  • 可以用这种方式在代码中拿到解析出来的token
// app.js文件 // koa-jwt token验证 const koaJwt = require('koa-jwt') const { secret } = require('./config').token app.use(koaJwt({secret, key: 'jwtToken'}).unless({ // 数组中的路径不需要通过jwt验证 path: [/^\/user\/*/, /\/captcha/,/^\/download/,/^\/uploads/] })) // 控制器等代码中 router.get('/test/token', async ctx => { const token = ctx.state.jwtToken ctx.success({ token: token }) })
  • 手动验证token方法
const { verifyToken } = require('../utils/user') router.get('/test/token', async ctx => { const token = 'xxxxxxxxxxxx' const res = await verifyToken(token) ctx.success({ token: res }) })

创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: