前后端通信中的 Token验证与 RSA加密
JSON Web Token
JSON Web Token(JWT)是一个轻量级的认证规范,这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。其本质是一个token,是一种紧凑的URL安全方法,用于在网络通信的双方之间传递。
JWT流程
- 客户端登录时向服务端发送请求
- 服务端验证通过后分发给前端一个
token
- 客户端存储
token
,并在后续请求中带上 - 服务端验证
token
有效性,并返回实际数据
token
结构
- 头部 (Header)
由两部分组成:令牌类型(即JWT),以及正在使用的签名算法,HMAC SHA256
或RSA
。然后将头部进行base64加密,形成第一部分。 - 载荷 (Payload)
负载有效数据的部分,由三部分组成:注册声明,公开声明和私有声明。- 注册声明:一组预定义的声明(建议但不强制使用);
- 公开声明:由使用者自定义(不建议添加敏感信息);
- 私有声明:提供者和消费者所共同定义的声明(不建议存放敏感信息);
然后将载荷进行base64加密,形成第二部分。
- 签名 (Signature)
这个部分需要 base64 加密后的header
和 base64 加密后的payload
使用.
连接组成的字符串,通过header
中声明的加密方式进行加盐secret
组合加密,然后形成第三部分。
使用
- 安装
yarn add jsonwebtoken
- 引用
const jwt = require('jsonwebtoken')
const PRIVATEKET = 'hello_jwt' // token 加密密钥
// 签发
const token = jwt.sign( info, PRIVATEKET, {expiresIn: '7d'})
// 验证
// 并提取负载信息
const info = jwt.verify(token, PRIVATEKET)
/**
* 业务使用
*/
// koa 登录接口
router.post('/login', async ctx => {
try {
const { userName, password } = ctx.request.body || {}
let results = await mysql.findUserName(userName)
let userInfo = results[0]
if (userInfo.password === password) {
const info = {userName: userInfo.name, userId: userInfo.id }
// 签发 token, 7天有效期
const token = jwt.sign( info, PRIVATEKET, {expiresIn: '7d'})
ctx.response.body = {code: 200, msg: '登录成功', data: {token: token, ...info}}
} else {
ctx.response.body = {code: 0, msg: '帐号不存在'}
}
} catch(err) {
ctx.response.body = {code: -1, msg: '服务器异常'}
throw new Error(err)
}
})
// 添加数据
router.post('/addComment', async ctx => {
const token = ctx.get('token') // 获取请求 Header 中 Authorization 值
if (!token) {
return ctx.response.body = {code: 0, msg: '未登录'}
}
try {
// 验证 token
// 验证失败会进入 catch
let info = jwt.verify(token, PRIVATEKET)
let {userId, comment} = ctx.request.body || {}
if(!userId) {
return ctx.response.body = {code: 0, msg: '数据错误'}
}
let {insertId} = await mysql.addComment(userId, comment)
ctx.response.body = {code: 0, data: {insertId}, msg: 'success'}
} catch(err) {
// token 过时或无效
ctx.response.body = {code: 0, msg: '未登录'}
}
})
node-rsa
1977年,三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字命名,叫做RSA算法。 -- RSA算法原理(一)阮一峰
非对称加密概念
- 乙方生成两把密钥(公钥和私钥)。公钥是公开的,任何人都能获取,私钥则是保密的。
- 甲方获取乙方的公钥, 然后用它对信息进行加密。
- 乙方得到加密后的信息, 用私钥解密。
安装
yarn add node-rsa
服务端
const NodeRSA = require('node-rsa')
class RSA {
constructor () {
this.key = null
this.publicKey = ''
this.privateKey = ''
}
initRsa () {
if(!this.key) {
let key = new NodeRSA({
b: 512
});
//指定加密格式 不改格式得话可能会报错
// key.setOptions({ encryptionScheme: 'pkcs1' });
this.key = key
this.publicKey = key.exportKey('public'); // 公钥
this.privateKey = key.exportKey('private'); // 私钥
}
return this.key;
}
decrypt (value) {
this.key.importKey(this.privateKey)
return this.key.decrypt(value, 'utf8')
}
}
// 业务模块
const rsa = new RSA()
// 分发公钥
router.get('/getPublicKey', async (ctx) => {
rsa.initRsa()
ctx.response.body = {
code: 200,
msg: 'success',
data: {
publicKey: rsa.publicKey
}
}
})
// 解密
// 沿用上部分案例
router.post('/login', async ctx => {
try {
const { userName, password } = ctx.request.body || {}
let results = await mysql.findUserName(userName)
let userInfo = results[0]
if (userInfo) {
// 解密后
const truthyPwd = rsa.decrypt(password, 'utf8')
if( userInfo.password != truthyPwd ){
return ctx.response.body = {code: 0, msg: '账号或密码错误'}
}
const info = {userName: userInfo.name, userId: userInfo.id }
// 签发 token, 7天有效期
const token = jwt.sign( info, PRIVATEKET, {expiresIn: '7d'})
ctx.response.body = {code: 200, msg: '登录成功', data: {token: token, ...info}}
} else {
ctx.response.body = {code: 0, msg: '帐号不存在'}
}
} catch(err) {
ctx.response.body = {code: -1, msg: '服务器异常'}
throw new Error(err)
}
})
客户端
import NodeRSA from 'node-rsa'
function entrypt(publicKey, value) {
// 生成RSA对象
const key = new NodeRSA({ b: 512 })
// 导入公钥
key.importKey(publicKey)
return key.encrypt(value, 'base64')
}
// 登录业务
async function request_login (name, pwd) {
try {
let {publicKey} = await request_getPublicKey() || {}
let pwdStr = encrypt(publicKey, pwd)
const res = await api.login(name, pwdStr);
const { token, userName, userId } = res.data || {};
return res.data;
} catch (err) {
console.log(err);
}
}