前后端登录逻辑讲解-本文使用RSA加密过程-附代码

时间:2023-01-25 17:52:15

一、项目环境

前端:React
后端:Nodejs(Eggjs)

二、生成公钥私钥

打开你的命令行进行生成公钥与私钥:

1.生成私钥

openssl genrsa -out rsa_1024_priv.pem 1024

前后端登录逻辑讲解-本文使用RSA加密过程-附代码

2.查看私钥

cat rsa_1024_priv.pem

前后端登录逻辑讲解-本文使用RSA加密过程-附代码

PS:这个私钥需要保存下来放到服务端,到时候解密需要
前后端登录逻辑讲解-本文使用RSA加密过程-附代码

3.生成公钥

openssl rsa -pubout -in rsa_1024_priv.pem -out rsa_1024_pub.pem

前后端登录逻辑讲解-本文使用RSA加密过程-附代码

4.查看公钥

cat rsa_1024_pub.pem

前后端登录逻辑讲解-本文使用RSA加密过程-附代码

PS:你可以把这个公钥copy到前端代码中,也可以copy到后端代码中然后通过接口暴露出来(推荐后者,原因是后续如果公钥和私钥要变化的时候,然后用公钥的地方就无需变动了,还是正常接口获取即可,否则就得每个地方把公钥替换一遍)

三、安装依赖包

PS:
前端需要安装的是:jsencrypt
后端需要安装的是:node-jsencrypt

// 前端的代码安装这个
npm install jsencrypt -S

// 后端的代码安装这个
npm install node-jsencrypt -S

四、自测加解密

1.纯前端自测

我们先把公钥私钥都放到前端,加解密也都在前端进行处理,看是否与我们预期一致。

前端登录的提交代码如下:

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);

前后端登录逻辑讲解-本文使用RSA加密过程-附代码

前端测试代码逻辑没问题了之后就可以把服务端代码移植过去了

2.前后端联调

①前端登录代码改为:

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中的参数被加密了
前后端登录逻辑讲解-本文使用RSA加密过程-附代码

2.然后后端日志输出的是自己输入的密码的时候就对了
前后端登录逻辑讲解-本文使用RSA加密过程-附代码

五、与数据库密码进行对比(数据库是进行了bcryptjs加密存储的)

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) {
		  // 报错处理:用户名或密码错误!
        }
      }
      // 成功处理:登录成功
    }
  }
};

六、服务端生成token与token校验

1.服务端安装依赖包

npm install egg-jwt --save

2.配置

找到 config\plugin.ts 文件进行补充:

jwt: { //jwt插件启用
  enable: true,
  package: 'egg-jwt',
}

前后端登录逻辑讲解-本文使用RSA加密过程-附代码


找到 config\config.default.js 文件在进行补充:

jwt: { //jwt加盐以及设置过期时间
  secret: 'jwt',
  expiresIn:'1h'
}

前后端登录逻辑讲解-本文使用RSA加密过程-附代码

3.生成token

在你自己需要生成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);

前后端登录逻辑讲解-本文使用RSA加密过程-附代码

4.服务端写个中间件验证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"];

前后端登录逻辑讲解-本文使用RSA加密过程-附代码

③前端代码编写

一般来讲都是登录之后后端返给前端token,然后前端后面的每次接口调用都将该token带到请求头里面

登录逻辑代码:

localStorage.setItem('token', res?.result?.token || '');

登录成功之后把token存到storage里面
前后端登录逻辑讲解-本文使用RSA加密过程-附代码
请求拦截代码:

// 请求拦截器
requestInterceptors: [
  (config: RequestOptions) => {
    // 拦截请求配置,进行个性化处理。
    const headers = { ...config.headers, token: localStorage.getItem('token') };
    return { ...config, headers };
  },
],

将token拼到请求头当中
前后端登录逻辑讲解-本文使用RSA加密过程-附代码

④联调自测

调用接口看前端token是否传过来了,token与存储的内容是否正确解析。
前后端登录逻辑讲解-本文使用RSA加密过程-附代码

PS: token过期时间可以设置短点,把token过期也自测一下

七、常见问题答疑(还有其他问题欢迎下方留言笔者进行补充解释):

问题一:为什么登录的时候要进行加密处理?

答:
可以使用户的密码不被泄露,即使可能在我们系统还是会受到重放攻击,但是不会将用户的原始密码暴露出去。
暴露出用户的原始密码可能会导致不法分子用该密码去尝试登录用户的其他平台(因为大部分人把所有软件设置的密码都大差不差甚至一样)。

问题二:为什么存数据库的时候要进行加密处理?

答:
因为保证内部人员或者数据库被盗之后不会对用户的损失过重。