SPRING CLOUD开发中,出于安全性考虑,到达EUREKA CLIENT的请求我们需要对其是否是通过网关做必要的验证。这样既可以保证请求在网关接受了前置性处理,也可以保证服务不会在集群外部被直接访问。
设计思路大概如下:
requestContext.addZuulRequestHeader("se-token", token);
requestContext.addZuulRequestHeader("ci-text", ciphertext);
se-token:随机生成的一个UUID。
ci-text:对se-token进行AES加密后的密文。
AES加密的公钥,分别放置在各服务与ZUUL的项目中。
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.LinkedList;
public class SecurityTokenUtil {
static LimitQueue queue = new LimitQueue<String>(2000);
static Object lock = new Object();
static final String SECURITY_KEY = "ThissTestkey18,.";
/**
* 通过两个header判断请求是否合法
* se-token : 随机生成的uuid
* ci-text : 密文
* @param request
* @return
*/
public static boolean checkRequestLegality(HttpServletRequest request) {
String token = request.getHeader("se-token");
String ciphertext = request.getHeader("ci-text");
if (token == null || token.equals("") || ciphertext == null || ciphertext.equals("")) return false;
return parseSecurityHeader(token,ciphertext,SECURITY_KEY);
}
public static boolean parseSecurityHeader(String token, String ciphertext, String key) {
//先查找这个token是不是已经在前面的请求被使用过了 如果已经使用过了 说明是伪造 返回false
synchronized (lock) {
if (queue.isExists(token)) {
//如果是伪造的 把这个token放入队列的最头部 加速定位
queue.remove(token);
queue.offer(token);
return false;
}
//如果token未使用 进行解码 看是否合法
try {
String decodedText = SymmetricEncoder.AESDncode(key, ciphertext);
//解码后数据与token不一致 返回false
if (decodedText != null && !decodedText.equals(token)) return false;
} catch (Exception e) {
return false;
}
//请求合法
queue.offer(token);
return true;
}
}
public static String tokenEncode(String token){
return SymmetricEncoder.AESEncode(SECURITY_KEY,token);
}
/**
* 有界队列 头插尾出
* @param
*/
private static class LimitQueue<E> {
private int limit;
private LinkedList<E> queue = new LinkedList<E>();
public LimitQueue(int limit) {
this.limit = limit;
}
public void offer(E e) {
if (queue.size() >= limit) {
queue.pollLast();
}
queue.offerFirst(e);
}
public boolean remove(E e) {
return queue.remove(e);
}
public E get(int position) {
return queue.get(position);
}
public E getLast() {
return queue.getLast();
}
public E getFirst() {
return queue.getFirst();
}
public int getLimit() {
return limit;
}
public int size() {
return queue.size();
}
public boolean isExists(E e) {
for (Iterator<E> iterator = queue.iterator(); iterator.hasNext(); ) {
E tmp = iterator.next();
if (tmp.equals(e)) {
return true;
}
}
return false;
}
}
public static class SymmetricEncoder {
/*
* 加密
* key 秘钥 content加密内容
* AES固定秘钥格式为128/192/256 bits.即:16/24/32bytes
*/
public static String AESEncode(String key, String content) {
try {
//1.两个参数,第一个为私钥字节数组, 第二个为加密方式
Key keySpec = new SecretKeySpec(key.getBytes(), "AES");
//2.根据指定算法AES自成密码器
Cipher cipher = Cipher.getInstance("AES");
//3.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二个参数为使用的KEY
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
//4.获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
byte[] byte_encode = content.getBytes("utf-8");
//5.根据密码器的初始化方式--加密:将数据加密
byte[] byte_AES = cipher.doFinal(byte_encode);
//6.将加密后的数据转换为字符串
//这里用Base64Encoder中会找不到包
//解决办法:
//在项目的Build path中先移除JRE System Library,再添加库JRE System Library,重新编译后就一切正常了。
String AES_encode = new String(new BASE64Encoder().encode(byte_AES));
//7.将字符串返回
return AES_encode;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//如果有错就返加nulll
return null;
}
/*
* 解密
* key 秘钥 content解密内容
* AES固定秘钥格式为128/192/256 bits.即:16/24/32bytes
*/
public static String AESDncode(String key, String content) {
try {
//1.构造密钥生成器,指定为AES算法,不区分大小写
Key keySpec = new SecretKeySpec(key.getBytes(), "AES");
//2.根据指定算法AES自成密码器
Cipher cipher = Cipher.getInstance("AES");
//3.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密(Decrypt_mode)操作,第二个参数为使用的KEY
cipher.init(Cipher.DECRYPT_MODE, keySpec);
//4.将加密并编码后的内容解码成字节数组
byte[] byte_content = new BASE64Decoder().decodeBuffer(content);
/*
* 解密
*/
byte[] byte_decode = cipher.doFinal(byte_content);
String AES_decode = new String(byte_decode, "utf-8");
return AES_decode;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
//如果有错就返加nulll
return null;
}
}
}
仅此记录加深印象,如有帮助,不胜荣幸。