1、定義需要加解密的Annotation
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EnableSecurity {
/**
*
* @return
*/
boolean ignored() default false;
/**
*
* @return
*/
boolean serverSide() default true;
/**
*
* @return
*/
Class target() default Object.class;
//方法參數加密字段
String[] encryptFields() default {};
//解密方法返回值字段
String[] decryptFields() default {};
}
2、服務端實現–數據加解密
1、證書生成
https://blog.csdn.net/iteye_7030/article/details/81965895
2、服務端證書讀取配置
public class ServerSecurityConfig{
private String password;
private String alias;
private String certificatePath;
private String keyStorePath;
@PostConstruct
public void afterPropertiesSet() throws Exception{
initCfg();
}
//@PostConstruct
public void initCfg() {
password = ContextConfig.get("aits.security.server.pwd", "passwd");
alias = ContextConfig.get("aits.security.server.alias", "aabbcc.com");
certificatePath = ContextConfig.get("aits.security.client.file", "/wls/envconfig/aits/server.cer");
keyStorePath = ContextConfig.get("aits.security.server.file", "/wls/envconfig/aits/server.keystore");
}
//
//get set ....
}
3、服務端SecurityServerRestTemplate
public class SecurityServerRestTemplate extends RestTemplate {
@Autowired(required = false)
private ServerSecurityConfig config;
private static final Logger log = LoggerFactory.getLogger(SecurityServerRestTemplate.class);
public SecurityServerRestTemplate() {
super();
this.getMessageConverters().add(new StringHttpMessageConverter(){
@Override
protected String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage)
throws IOException {
String data = super.readInternal(clazz, inputMessage);
try {
byte[] decrypt = CertificateCoder.decryptByPrivateKey(
CertificateCoder.decryptBASE64(data),
config.getKeyStorePath(), config.getAlias(), config.getPassword());
data = new String(decrypt);
}catch (Exception ex){
log.error("error-encode-data:{}",data,ex);
}
return data;
}
@Override
protected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {
//服務端加密str
try {
byte[] encodedData = CertificateCoder.encryptByPrivateKey(str.getBytes(),
config.getKeyStorePath(), config.getAlias(), config.getPassword());
str = CertificateCoder.encryptBASE64(encodedData);
}catch (Exception ex){
log.error("error-encode-data:{}",str,ex);
}
super.writeInternal(str, outputMessage);
}
});
}
}
3、客戶端實現-數據加解密
1、客戶端證書讀取配置
public class ClientSecurityConfig {
private String certificatePath;
@PostConstruct
public void afterPropertiesSet() throws Exception{
initCfg();
}
public void initCfg() {
certificatePath = ContextConfig.get("aits.security.client.file",
"/wls/envconfig/aits/server.cer");
}
public String getCertificatePath() {
return certificatePath;
}
public void setCertificatePath(String certificatePath) {
this.certificatePath = certificatePath;
}
}
2、客戶端SecurityClientRestTemplate
/**
* response 實體整個加密傳輸,讀取後整體解密
* note 傳輸過程中,必須base64加解密
* @author WongBin
* @date 2019/2/26
*/
public class SecurityClientRestTemplate extends RestTemplate {
private static final Logger log = LoggerFactory.getLogger(SecurityServerRestTemplate.class);
public SecurityClientRestTemplate() {
super();
this.getMessageConverters().clear();
this.getMessageConverters().add(0,new StringHttpMessageConverter(){
@Override
public String readInternal(Class<? extends String> clazz, HttpInputMessage inputMessage) throws IOException {
String data = super.readInternal(clazz, inputMessage);
//客戶端解密
try {
log.info("==========data-size:{}",data.length());
byte[] bts = CertificateCoder.decryptBASE64(data);
byte[] decrypt = CertificateCoder.decryptByPublicKey(bts,config.getCertificatePath());
data = new String(decrypt);
log.info("==========read-decrypted");
}catch (Exception ex){
log.error("error-decode-data:{}",data,ex);
}
return data;
}
@Override
protected void writeInternal(String data, HttpOutputMessage outputMessage) throws IOException {
//客戶端加密str
try {
String d = CertificateCoder.encryptBASE64(data.getBytes());
byte[] encodedData = CertificateCoder.encryptByPublicKey(d.getBytes(),config.getCertificatePath());
//str = new String(encodedData);
data = new String(encodedData);
log.info("==========to-write-encrypted");
}catch (Exception ex){
log.error("error-encode-data:{}","",ex);
}
super.writeInternal(data, outputMessage);
}
});
//this.getMessageConverters().add(new StringHttpMessageConverter());
}
@Autowired(required = false)
private ClientSecurityConfig config;
}
相關輔助類
/**
* RSA加密的數據,網絡傳輸之前必須base64加密,本地獲取後首先base64解密,再做後續解密操作
*
* @author WongBin
* @date 2019/2/26
*/
public abstract class Coder {
public static String encryptBASE64(byte[] data){
return new String(Base64Utils.encode(data));
}
public static byte[] decryptBASE64(String data){
return Base64Utils.decode(data.getBytes());
}
}
CertificateCoder實現參考以下文章:https://blog.csdn.net/iteye_7030/article/details/81965895
1 、客戶端數據加解密組件:
/**
* @author WongBin
* @date 2019/2/27
*/
//@Component 調用方負責實例化
public class ClientDataResolver {
private static final Logger log = LoggerFactory.getLogger(ClientDataResolver.class);
@Autowired(required = false)
private ClientSecurityConfig config;
/***
* 獲取服務端數據後解密
* @param serverData
* @return
*/
public String decode(String serverData){
try {
return new String(CertificateCoder.decryptByPublicKey(
CertificateCoder.decryptBASE64(serverData), config.getCertificatePath()));
}catch (Exception ex){
log.error("decode-server-data-error:{}",serverData,ex);
return serverData;
}
}
/**
* 發送給服務端之前 加密
* @param clientData
* @return
*/
public String encode(String clientData){
try {
return new String(CertificateCoder.encryptBASE64(
CertificateCoder.encryptByPublicKey(clientData.getBytes(),
config.getCertificatePath())));
}catch (Exception ex){
log.error("encode-client-data-error:{}",clientData,ex);
return clientData;
}
}
/***
* 解密服務端返回的 加密對象
*
* @param data
*/
public void resolveSecurityFields(Object data)throws Exception{
if(data!=null && data.getClass().isAnnotationPresent(EnableSecurity.class)){
EnableSecurity tag = data.getClass().getAnnotation(EnableSecurity.class);
if (!tag.serverSide()) {
Class<?> resultClz = data.getClass();
Field[] fieldInfo = resultClz.getDeclaredFields();
try {
for (String f : tag.decryptFields()) {
for (Field field : fieldInfo) {
if (f.equals(field.getName())) {
field.setAccessible(true);
String t = (String)field.get(data);
try {
byte[] bts = CertificateCoder.decryptBASE64(t);
byte[] temp = CertificateCoder.decryptByPublicKey(bts,config.getCertificatePath());
field.set(data, new String(temp));
log.info("decrypt-server-data-done:...{}", f);
} catch (Exception ex) {
//log.error("decrypt-server-data-error:{}", data, ex);
throw ex;
}
break;
}
}
}
} catch (Exception ex) {
log.error("解密服務端數據出錯:{}",data, ex);
throw ex;
}
}
}
}
}
2、服務端數據加解密組件
/**
* @author WongBin
* @date 2019/2/27
*/
public class ServerDataResolver {
private static final Logger log = LoggerFactory.getLogger(ServerDataResolver.class);
@Autowired
private SecurityServerRestTemplate template;
@Autowired
private ServerSecurityConfig config;
/***
* 獲取客戶端 數據後解密
* @param data
* @return
*/
public String decode(String data){
try {
return new String(CertificateCoder.decryptByPrivateKey(
CertificateCoder.decryptBASE64(data),
config.getKeyStorePath(),config.getAlias(),config.getPassword()));
}catch (Exception ex){
log.error("decode-client-data-error:{}",data,ex);
return data;
}
}
/**
* 發送給 客戶端之前 加密
* @param data
* @return
*/
public String encode(String data){
try {
return new String(CertificateCoder.encryptBASE64(
CertificateCoder.encryptByPrivateKey(data.getBytes(),
config.getKeyStorePath(),config.getAlias(),config.getPassword())));
}catch (Exception ex){
log.error("encode-server-data-error:{}",data,ex);
return data;
}
}
/***
* 解密客戶端返回的 加密對象
*
* @param data
*/
public void resolveSecurityFields(Object data){
if(data!=null && data.getClass().isAnnotationPresent(EnableSecurity.class)){
EnableSecurity tag = data.getClass().getAnnotation(EnableSecurity.class);
if (!tag.serverSide()) {
Class<?> resultClz = data.getClass();
Field[] fieldInfo = resultClz.getDeclaredFields();
try {
for (String f : tag.decryptFields()) {
for (Field field : fieldInfo) {
if (f.equals(field.getName())) {
field.setAccessible(true);
String t = (String)field.get(data);
try {
byte[] bts = CertificateCoder.decryptBASE64(t);
byte[] temp = CertificateCoder.decryptByPrivateKey(bts,
config.getKeyStorePath(),config.getAlias(),config.getPassword());
field.set(data, new String(temp));
log.info("decrypt-client-data-done:...{}", f);
} catch (Exception ex) {
log.error("decrypt-client-data-error:{}", data, ex);
}
break;
}
}
}
} catch (Exception ex) {
log.error("解密客戶端返回的數據出錯:{}",data, ex);
}
}
}
}
}
4、使用說明
1 客戶端實例化相關組件
@Bean
@Lazy
public ClientSecurityConfig clientSecurityConfig(){
return new ClientSecurityConfig();
}
@Bean
//@DependsOn({"clientSecurityConfig"})
@Lazy
public ClientDataResolver clientDataResolver(){
return new ClientDataResolver();
}
@Bean
//@DependsOn({"clientSecurityConfig"})
@Lazy
public SecurityClientRestTemplate securityClientRestTemplate(){
return new SecurityClientRestTemplate();
}
@Lazy
@Primary
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
定義需要向服務端加密傳輸的對象
@EnableSecurity(serverSide = false,decryptFields = {"srcDbIp","srcDbPort","srcDbUsername","srcDbPasswd","srcDbname"})
public class DbConfigVO {
private String dbType;
private String dbFile;
private String projectCode;
private String srcDbIp;
// get set toString...
}
加密
@Autowired
private ClientDataResolver clientDataResolver;
....
clientDataResolver.resolveSecurityFields(vo);
....
服務端實例化相關組件
@Configuration
public class SecurityConfig {
/*數據加密相關組件*/
@Bean
//@DependsOn({"serverSecurityConfig"})
@Lazy
public SecurityServerRestTemplate securityTemplate(){
return new SecurityServerRestTemplate();
}
@Bean
@Primary
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
@Lazy
public ServerDataResolver resolver(){
return new ServerDataResolver();
}
@Lazy
@Bean
public ServerSecurityConfig serverSecurityConfig(){
return new ServerSecurityConfig();
}
}
服務端解密客戶端加密的數據
@Autowired
private ServerDataResolver dataResolver;
dataResolver.resolveSecurityFields(...)
當然,服務端加密數據給客戶端,可以定義Aspect統一處理EnableSecurity標記的類,目前已實現內部項目,不便於公開,有需要留言溝通。
服務端加密傳輸,客戶端解密
客戶端加密傳輸,服務端解密
最終雙向加密傳輸都可以實現了,有類似需求的可以參考實現之。
- 附加工具類
/**
* String轉公鑰PublicKey
* @param key
* @return
* @throws Exception
*/
public static PublicKey getPublicKey(String key){
byte[] keyBytes;
try {
keyBytes = (new BASE64Decoder()).decodeBuffer(key);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
return publicKey;
}catch (Exception ex){
throw new RuntimeException("getPublicKey",ex);
}
}
/**
* String轉私鑰PrivateKey
* @param key
* @return
* @throws Exception
*/
public static PrivateKey getPrivateKey(String key){
byte[] keyBytes;
try {
keyBytes = (new BASE64Decoder()).decodeBuffer(key);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
return privateKey;
}catch (Exception ex){
throw new RuntimeException("getPrivateKey-error",ex);
}
}