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版权协议,转载请附上原文出处链接和本声明。