Java校园淘项目实战(1)————JWT+RSA非对称加密生成token

springCloud环境配置好了,接下来就是先把工具类写好,因为接下来要写用户接口微服务,所有打算先把密钥工具类和生成token的工具类写好。
(1)yml配置,里面声明了公钥和私钥存放的地方。

server:
  port: 10021
spring:
  application:
    name: commons-service
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka/
    registry-fetch-interval-seconds: 10
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1
    instance-id: ${spring.application.name}:${server.port}
    lease-renewal-interval-in-seconds: 5
    lease-expiration-duration-in-seconds: 20

schooltao:
  rsa:
    publicKeyPath: "D:\\schoolProject\\rsa\\rsaPub.txt"
    privateKeyPath: "D:\\schoolProject\\rsa\\rsaPri.txt"
    secure: "schooltao"
    cookieName: "TAO_TOKEN"

(2)token使用私钥加密,公钥解密。
token的载荷对象,里面包含了id和name,没有把密码放在里面,是为了更加安全

package com.tao.common.pojo;

public class UserInfo {
    /**
     * 用户ID
     */
    private Long userId;
    /**
     * 用户账号
     */
    private String userName;


    public UserInfo() {
    }

    public UserInfo(Long userId, String userName) {
        this.userId = userId;
        this.userName = userName;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

(3)密钥工具类
注意:大家一定要记住,必须要把公钥和私钥生成的字节数组经过base64加密,因为token是采用base64加密的,在获取公钥和私钥的时候,一定要记得使用base64解密。

package com.tao.common.utils;


import org.apache.commons.io.FileUtils;
import org.bouncycastle.util.encoders.Base64Encoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Encoder;

import java.io.*;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * RSA非对称加密工具类
 */
public class RsaUtils {

    /**
     * 加密算法
     */
    private static final String KEY_ALGORITHM = "RSA";

    /**
     * 签名(signature)算法
     */
    private static final String SIGNATURE_ALGORITHM = "MD5withRSA";

    /**
     * 获取公钥的key
     */
    public static final String PUBLIC_KEY = "RSAPublicKey";

    /**
     * 获取私钥的key
     */
    public static final String PRIVATE_KEY = "RSAPrivateKey";

    /**
     * RSA最大加密密文大小
     */
    private static final Integer MAX_ENCRYPT = 245;

    /**
     * RSA最大解密密文大小
     */
    private static final Integer MAX_DECRYPT = 256;

    /**
     * RSA位数,采用2048,则加密密文和解密密文需要使用245和256
     */
    private static final Integer INITIALIZE_LENGTH = 2048;

    private static final Logger logger = LoggerFactory.getLogger(RsaUtils.class);

    /**
     * 生成密钥对(公钥和私钥)
     * 并把公钥和私钥保存再服务器上
     * secure: 生成密钥的密文
     *
     * @return
     */
    public static void createKeyPair(String publicKeyPath,
                                     String privateKeyPath,
                                     String secure) throws NoSuchAlgorithmException, IOException {
        //获得生成器
        KeyPairGenerator instance = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        SecureRandom secureRandom = new SecureRandom(secure.getBytes());
        //初始化,限定大小
        instance.initialize(INITIALIZE_LENGTH, secureRandom);
        //获得钥匙对
        KeyPair keyPair = instance.generateKeyPair();
        byte[] pubEncoding = keyPair.getPublic().getEncoded();
        byte[] priEncoding = keyPair.getPrivate().getEncoded();
        byte[] encode = Base64.getEncoder().encode(pubEncoding);
        byte[] encode1 = Base64.getEncoder().encode(priEncoding);
        writeKeyToFile(publicKeyPath, encode);
        writeKeyToFile(privateKeyPath, encode1);
    }



    /**
     * 根据公钥的存放路径读取公钥
     * @param publicKeyFile
     * @return
     * @throws IOException
     * @throws NoSuchAlgorithmException
     */
    public static PublicKey getxPublicKey(String publicKeyFile) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] bytes = readKeyFormFile(publicKeyFile);
        return getPublicKey(bytes);
    }
    /**
     * 根据公钥的字节数据,返回公钥
     * @param date  公钥的字节数据
     * @return 返回公钥
     * @throws NoSuchAlgorithmException
     * @throws IOException
     */
    public static PublicKey getPublicKey(byte[] date) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
        //解密base64加密后的数据
        byte[] bytes = Base64.getDecoder().decode(date);
        KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(bytes);
        return factory.generatePublic(x509EncodedKeySpec);
    }

    /**
     * 根据私钥的存放路径获取公钥
     * @param privateKeyFile
     * @return
     * @throws InvalidKeySpecException
     * @throws NoSuchAlgorithmException
     */
    public static PrivateKey getPrivateKey(String privateKeyFile) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException {
        byte[] bytes = readKeyFormFile(privateKeyFile);
        return getPrivateKey(bytes);
    }

    /**
     * 根据私钥的字节数据生成私钥
     *
     * @param date 私钥的字节数据
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static PrivateKey getPrivateKey(byte[] date) throws NoSuchAlgorithmException, InvalidKeySpecException {
        //解码,获得原来的key
        byte[] bytes = Base64.getDecoder().decode(date);
        KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(bytes);
        return factory.generatePrivate(pkcs8EncodedKeySpec);
    }

    /**
     * 从指定文件中读取公钥或者私钥的字节数组
     *
     * @param keyFile
     * @return 公钥或者私钥的字节数组
     */
    public static byte[] readKeyFormFile(String keyFile) throws IOException {
        return Files.readAllBytes(new File(keyFile).toPath());
    }


    /**
     * 把数据写入到指定的文件
     * @param keyFile
     * @param bytes
     * @throws IOException
     */
    public static void writeKeyToFile(String keyFile, byte[] bytes) throws IOException {
        File file = new File(keyFile);
        if (!file.exists()) {
            file.createNewFile();
        }
        Files.write(file.toPath(), bytes);
    }


}

(4)token工具类,这里生成的token默认为30分钟有效时间。大家一定要引入jjwt依赖,不然会报错。token的加密过程为:在生成token的时候,利用私钥作为签名。在解密token的时候,就会用到公钥。

package com.tao.common.utils;

import com.google.inject.internal.cglib.core.$ReflectUtils;

import com.tao.common.pojo.UserInfo;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Calendar;
import java.util.Date;

/**
 * jwt工具类
 */
public class JwtUtils {

    /**
     *
     * @param userInfo 载荷对象
     * @param privateKey 加密使用的私钥
     * @return 带有半个小时过期的token
     */
    public static String createToken(UserInfo userInfo, PrivateKey privateKey) {
        //获取过期时间,30分钟
        Date date = new Date();
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.MINUTE, 30);
        Date endTime = instance.getTime();
        //使用私钥生成加密token
        String token = Jwts.builder()
                .claim(JwtContains.JWT_USER_ID, userInfo.getUserId())
                .claim(JwtContains.JWT_USER_NAME, userInfo.getUserName())
                .setExpiration(endTime)
                .signWith(SignatureAlgorithm.RS256, privateKey)
                .compact();
        System.out.println(token);
        return token;
    }



    /**
     * 利用公钥解析token,从token中获取到载荷对象(UserInfo)
     * @param token
     * @param publicKey
     * @return
     */
    public static UserInfo getUserInfoFromToken(String token, PublicKey publicKey) {
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
        Claims body = claimsJws.getBody();
        Long id = Long.valueOf(String.valueOf(body.get(JwtContains.JWT_USER_ID)));
        String username = String.valueOf(body.get(JwtContains.JWT_USER_NAME));
        return new UserInfo(id, username);
    }

}

(5)考虑到用户注册的时候,需要把密码加密,所有随便把salt工具类写了吧。

package com.tao.common.utils;

import com.google.inject.internal.cglib.core.$ReflectUtils;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;

/**
 * 随机产生用于加密密码的盐
 */
public class SaltUtils {

    /**
     * 随机产生经过md5加密后的盐
     * @return
     */
    public static String createSalt() {
        Random random = new Random();
        StringBuffer stringBuffer = new StringBuffer();
        for (int i = 0; i < 20; i++) {
            stringBuffer.append(random.nextInt(10));
        }
        //md5加密后的盐
        return md5Encrypt(stringBuffer.toString());

    }

    /**
     * 对字符串数据进行简单的md5加密,然后返回加密的字符串
     * @param date
     * @return
     */
    public static String md5Encrypt(String date) {
        byte[] md5s = null;
        String salt = null;
        try {
            md5s = MessageDigest.getInstance("md5").digest(date.getBytes());
        } catch (NoSuchAlgorithmException e) {
            System.out.println("md5加密失败");
            e.printStackTrace();
        }
        //转为16进制的字符串
        salt = new BigInteger(1, md5s).toString(16);
        return salt;
    }

}

总结:RSA非对称加密,加密的安全性比较高,公钥和私钥不一致,不过把公钥和私钥写入本地文件的时候,特别要注意,不然很容易出错。token里面包含了载荷对象(用户id和用户名),签名使用私钥,公钥存放在服务器,一定不要被别人获取到。
今天的内容不多,但是作为新手,还是花了一天的时间才写完,经过测试,自己也学到了很多。下次我会把用户登录的接口完成,喜欢的话,可以关注。


版权声明:本文为China_TomCat原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。