前端:React
后端:Nodejs(Eggjs)
打开你的命令行进行生成公钥与私钥:
openssl genrsa -out rsa_1024_priv.pem 1024
cat rsa_1024_priv.pem
openssl rsa -pubout -in rsa_1024_priv.pem -out rsa_1024_pub.pem
cat rsa_1024_pub.pem
PS:你可以把这个公钥copy到前端代码中,也可以copy到后端代码中然后通过接口暴露出来(推荐后者,原因是后续如果公钥和私钥要变化的时候,然后用公钥的地方就无需变动了,还是正常接口获取即可,否则就得每个地方把公钥替换一遍)
PS:
前端需要安装的是:jsencrypt
后端需要安装的是:node-jsencrypt
// 前端的代码安装这个
npm install jsencrypt -S
// 后端的代码安装这个
npm install node-jsencrypt -S
我们先把公钥私钥都放到前端,加解密也都在前端进行处理,看是否与我们预期一致。
前端登录的提交代码如下:
import JSEncrypt from 'jsencrypt';
/** 这里只写了方法里面的具体内容哈 */
// 加密
const publicKey = '这里放你的公钥';
console.log('吴迪想看的:', values);
const encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);
const encrypted = encrypt.encrypt(values.password);
console.log('加密后: ', encrypted);
// 解密
const privateKey = '这里放你的私钥';
const decrypt = new JSEncrypt();
decrypt.setPrivateKey(privateKey);
const uncrypted = decrypt.decrypt(encrypted as string);
console.log('解密后:', uncrypted);
前端测试代码逻辑没问题了之后就可以把服务端代码移植过去了
const handleSubmit = async (values: API.LoginParams) => {
try{
// 把密码加密登录
const publicKey = '替换你的公钥';
const encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);
const encrypted = encrypt.encrypt(values.password as string || '');
// 调用你的登录接口然后处理参数
const res = await login({
...values,
password: encrypted as string,
});
// 后续自行根据返回值做成功和失败处理
} catch (error) {
// 错误处理
}
};
import { Controller } from 'egg';
const JSEncrypt = require('node-jsencrypt');
module.exports = app => {
return class UserController extends Controller {
public async login() {
const privateKey = '替换你自己的私钥'
const { ctx } = this;
const data = ctx.request.body;
// 解密
const decrypt = new JSEncrypt();
decrypt.setPrivateKey(privateKey);
const uncrypted = decrypt.decrypt(data.password as string);
console.log('解密后:', uncrypted);
// TODO:数据库处理
ctx.body = await ctx.service.test.sayHi(data.username);
}
}
};
当我们发现调接口的时候需要验证俩个地方
1.浏览器上的network中的参数被加密了
import { Controller } from 'egg';
const JSEncrypt = require('node-jsencrypt');
const bcrypt = require('bcryptjs');
// 判断密码是否输入正确
const comparePassword = (userInputPassword: string, enCodePassword: string) => {
return bcrypt.compareSync(userInputPassword, enCodePassword);
};
// 解密RSA加密
const deRSACode = (password: string, privateKey: string) => {
const decrypt = new JSEncrypt();
decrypt.setPrivateKey(privateKey);
return decrypt.decrypt(password);
}
module.exports = app => {
return class UserController extends Controller {
public async login(ctx) {
const data = ctx.request.body;
// 这里第二个参数自行替换你的私钥
const uncrypted = deRSACode(data.password as string, app.config.privateKey);
// 数据库处理
// 这里替换成你自己的数据库表名和字段名
const userInfo = await app.mysql.get('user', { login_name: data.username });
console.log('userInfo: ', userInfo);
if (!userInfo) {
return ctx.failure("用户名或密码错误!");
}
if (userInfo) {
// 第一个参数是用户输入的密码,第二个参数是数据库中加密的密码
const isPasswordRight = comparePassword(uncrypted, userInfo.password);
if (!isPasswordRight) {
// 报错处理:用户名或密码错误!
}
}
// 成功处理:登录成功
}
}
};
npm install egg-jwt --save
找到 config\plugin.ts 文件进行补充:
jwt: { //jwt插件启用
enable: true,
package: 'egg-jwt',
}
找到 config\config.default.js 文件在进行补充:
jwt: { //jwt加盐以及设置过期时间
secret: 'jwt',
expiresIn:'1h'
}
在你自己需要生成token的controller里面写:
/**
* 生成token
* 第一个参数是存储参数
* 第二个参数是加盐
* 第三个参数是配置,比如过期时间
* */
const options = {
expiresIn: app.config.jwt.expiresIn,
};
const token = ctx.app.jwt.sign(
{ ...userInfo },
app.config.jwt.secret,
options
);
console.log('token: ', token);
我们新建个文件:app\middleware\jwtValidate.ts
module.exports = (_, app) => {
return async function (ctx, next) {
// 设置跳过验证的token的路由白名单,比如笔者把登录接口跳过了
const routerAuth = ['/api/login/account'];
// 获取当前路由
const url = ctx.url;
// 判断当前路由是否需要跳过验证token
if (routerAuth.includes(url)) {
await next();
} else {
// 获取token
var token = ctx.headers.token ? ctx.headers.token : '';
console.log("获取token", token)
// 解析token
const throwError = () => {
ctx.body = {
code: 401,
errorMessage: 'token失效或解析错误',
data: null
};
};
try {
const decode = await app.jwt.verify(token, app.config.jwt.secret);
console.log("解析出来的数据:", decode)
if (decode) {
await next();
} else {
throwError();
}
} catch (err) {
// app.logger.debug(err); //打印错误日志
throwError();
}
}
}
}
找到文件 config\config.default.ts 启用中间件
config.middleware = ["jwtValidate"];
一般来讲都是登录之后后端返给前端token,然后前端后面的每次接口调用都将该token带到请求头里面
登录逻辑代码:
localStorage.setItem('token', res?.result?.token || '');
登录成功之后把token存到storage里面
请求拦截代码:
// 请求拦截器
requestInterceptors: [
(config: RequestOptions) => {
// 拦截请求配置,进行个性化处理。
const headers = { ...config.headers, token: localStorage.getItem('token') };
return { ...config, headers };
},
],
调用接口看前端token是否传过来了,token与存储的内容是否正确解析。
PS: token过期时间可以设置短点,把token过期也自测一下
答:
可以使用户的密码不被泄露,即使可能在我们系统还是会受到重放攻击,但是不会将用户的原始密码暴露出去。
暴露出用户的原始密码可能会导致不法分子用该密码去尝试登录用户的其他平台(因为大部分人把所有软件设置的密码都大差不差甚至一样)。
答:
因为保证内部人员或者数据库被盗之后不会对用户的损失过重。