bcrypt
数据库中存放的用户密码不能是明文的
bcrypt
是单向的,而且经过salt
和cost
的处理,使其受rainbow
攻击破解的概率大大降低,同时破解的难度也提升不少,相对于MD5
等加密方式更加安全,而且使用也比较简单
-
下载
bcrypt
:npm 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 Base
,JWT
(Json Web Tokens
)作为一个开放的标准被很多网站采用
koa-jwt
这个中间件使用JWT
认证HTTP
请求。
jwt
是为了在网络应用环境间传递声明而执行的一种基于JSON
的开放标准, 主要有三部分组成
Header
标头通常由两部分组成:令牌的类型,即JWT
,以及正在使用的签名算法(例如HMAC SHA256
或RSA
)
// 这个JSON被编码为Base64Url,形成JWT的第一部分
{
"alg": "HS256",
"typ": "JWT"
}
Payload
令牌的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和其他数据的声明
// 这个JSON被编码为Base64Url,形成JWT的第二部分
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Signature
要创建签名部分,您必须采用编码标头,编码的有效负载,秘钥,标头中指定的算法,并对其进行签名
// 以HMAC SHA256算法为例
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
koa-jwt
koa-jwt
工作流程
- 用户通过登录
Api
获取当前用户在有效期内的token
- 需要身份验证的
API
则都需要携带此前认证过的token
发送至服务端koa2
会利用koa-jwt
中间件的默认验证方式进行身份验证,中间件会进行验证成功和验证失败的分流。
koa-jwt
主要提供路有权限控制的功能,它会对需要限制的资源请求进行检查
koa-jwt
请求头中设置authorization
为Bearer
+ token
,Bearer
后有空格。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
依赖于jsonwebtoken
和koa-unless
两个库的
-
下载
jsonwebtoken koa-jwt
:npm 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
})
})
发表评论