加密登录逻辑

813#52d1c3d4

登录包括了:记住用户名密码、密码加密、用户名密码校验等。这里简单谈谈我使用过的流程内容。

实际的功能肯定是吃需求的。所以,这里假设两种需求:

情景一

用户登录要求能记住用户名密码,并且在某一段时间后过期。

情景二

同样要求记住密码,但不需要过期。

情景一是多数商业化的系统要求,情景二则可能是个人的小系统要求,满足自己可以(比如我?)

流程

输入信息 -> 勾选记住 -> 调用登录接口 -> 存储信息 -> 页面跳转(流程图软件出问题, 将就一下)

勾选记住及之前的都是用户操作步骤,这里不讨论怎么web实现,主要聊的是从调用接口校验登录信息到页面跳转之前的内容。

构思

主要考虑两个问题:存储信息和加密信息。

存储信息

情景一中记住用户名密码功能,想在一定时间后过期,又不要自己写额外的代码控制的话,最好的方式是将信息存在Cookie中;情景二中的话,localStorage就是不二之选了。

加密信息

要明白一个问题,加密完后的信息,是要能够解密的。比如,记住用户名密码后,下次登录读取出来密文,可以通过某种算法还原信息。

目前比较合适的一个用户名密码加解密算法是肯定就是RSA,靠谱!

为了保证接口传输信息在被劫持下来后没有作用,RSA依赖的公钥在每次请求时重新生成,逻辑即是调用登录接口前调用一个获取公钥的接口。

但是这只能用户接口信息传输上的加解密,存储信息没有很可靠的办法做到不被解密(因为存储的信息是在用户端上,而RSA需要的秘钥就没办法作为秘钥存放在用户端上,js代码又是公共资源),所以存储信息只能自我安慰的加密解密,这里就采用AES实现。

实现

加解密算法原理可以学习学习,代码咱就不自己写了,用用库就OK了,这里用到两个库:crypto-jsjsencrypt,前一个主要用来调用AES算法加解密存储信息(记住密码),后一个主要用来调用RSA算法加密(登录)接口传输信息。

shell 复制代码
yarn add crypto-js jsencrypt

用户信息不过期

  • 前端代码
js 复制代码
import JSEncrypt from 'jsencrypt'
import aes from 'crypto-js/aes'
import encUtf8 from 'crypto-js/enc-utf8'

// AES依赖的key
const KEY = ':!vi_Vv&kijrUIxu*vV:EaK'

// 用户名
const NAME_KEY = 'olw5dgto8leiolz5'
// 密码
const PWD_KEY = 'kio7udc5utkcel0o'
// 是否记住
const CACKE_KEY = '2fgaobbcjkm0r59e'

const user = {
  name: 'zbf',
  password: '123456'
}

// 记住用户信息状态
const cache = true

// 读出存储的信息
const init = () => {
  const _name = localStorage.getItem(NAME_KEY)
  const _password = localStorage.getItem(PWD_KEY)
  const _cache = localStorage.getItem(CACKE_KEY)

  if (name && password) {
    user.name = aes.decrypt(_name, KEY).toString(encUtf8)
    user.password = aes.decrypt(_password, KEY).toString(encUtf8)
    cache = !!_cache
  }
}

// 本地存储用户信息
const store = () => {
  // 判断是否记住用户信息
  if(cache) {
    localStorage.setItem(NAME_KEY, aes.encrypt(user.name, KEY).toString())
    localStorage.setItem(PWD_KEY, aes.encrypt(user.password, KEY).toString())
    // 记住用户信息状态
    localStorage.setItem(CACKE_KEY, cache)
  }
}

// validate已通过
// 获取公钥
axios.get('/pubKey').then(({ data }) => {
  const encrypt = new JSEncrypt()
  encrypt.setPublicKey(data.key)

  // 尝试登陆
  axios.post('/login', {
      name: encrypt.encrypt(user.name),
      password: encrypt.encrypt(user.password)
    })
    .then(({ data }) => {
      // 登录成功,存储数据
      data.code === 0 && store()
      // 其他错误判断
    })
})
  • 服务端代码

服务端需要提供一个生成公钥的接口,一个根据私钥解密信息的接口,/pubKey接口生成公私钥,返回公钥,session存储私钥,/login接口获取session私钥,解密传输信息。

算法方法,这是我找到的网上的一个原文https://blog.csdn.net/qy20115549/article/details/83105736

java 复制代码
package com.zhoubangfu.util;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

public class RSAEncrypt {
  /**
   * 随机生成密钥对
   */
  public static Map<Integer, String> genKeyPair() throws NoSuchAlgorithmException {
    // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
    KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
    // 初始化密钥对生成器,密钥大小为96-1024位
    keyPairGen.initialize(1024, new SecureRandom());
    // 生成一个密钥对,保存在keyPair中
    KeyPair keyPair = keyPairGen.generateKeyPair();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥
    String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
    // 得到私钥字符串
    String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
    // 将公钥和私钥保存到Map

    Map<Integer, String> keyMap = new HashMap<>();
    keyMap.put(0, publicKeyString);  //0表示公钥
    keyMap.put(1, privateKeyString);  //1表示私钥

    return keyMap;
  }

  /**
   * RSA公钥加密
   *
   * @param str       加密字符串
   * @param publicKey 公钥
   * @return 密文
   * @throws Exception 加密过程中的异常信息
   */
  public static String encrypt(String str, String publicKey) throws Exception {
    //base64编码的公钥
    byte[] decoded = Base64.decodeBase64(publicKey);
    RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
    //RSA加密
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, pubKey);
    return Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
  }

  /**
   * RSA私钥解密
   *
   * @param str        加密字符串
   * @param privateKey 私钥
   * @return 铭文
   * @throws Exception 解密过程中的异常信息
   */
  public static String decrypt(String str, String privateKey) throws Exception {
    //64位解码加密后的字符串
    byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
    //base64编码的私钥
    byte[] decoded = Base64.decodeBase64(privateKey);
    RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
    //RSA解密
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.DECRYPT_MODE, priKey);
    return new String(cipher.doFinal(inputByte));
  }
}

获取公钥

java 复制代码
private static final String PRI_KEY = "xxxxx";

@GetMapping("/pubKey")
public ResultUtil pubKey(HttpServletRequest request) throws NoSuchAlgorithmException {
  HttpSession session = request.getSession();
  Map<Integer, String> map = RSAEncrypt.genKeyPair();
  session.setAttribute(PRI_KEY, map.get(1));
  return ResultUtil.ok().put("data", map.get(0));
}

解密信息

java 复制代码
@PostMapping("/login")
public ResultUtil login(@RequestBody UserModel user, HttpServletRequest request) throws Exception {
  HttpSession session = request.getSession();
  user.setUsername(RSAEncrypt.decrypt(user.getUsername(), (String) session.getAttribute(PRI_KEY)));
  user.setPassword(Encrypt.SHA256(RSAEncrypt.decrypt(user.getPassword(), (String) session.getAttribute(PRI_KEY))));

  // 验证逻辑

  return ResultUtil.ok();
}

用户信息过期

改用Cookie存数据就可以了~~

结束

demo代码写的比较快,就没有优化了,勿喷

参与本文讨论

请先登录 GitHub 后留言

0/500

本文留言

0

这篇文章还没有留言,来写第一条吧。

1 / 1