Springboot应用-具有Security特性的RestTemplate

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;@PostConstructpublic void afterPropertiesSet() throws Exception{initCfg();}//@PostConstructpublic 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(){@Overrideprotected 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;}@Overrideprotected void writeInternal(String str, HttpOutputMessage outputMessage) throws IOException {//服务端加密strtry {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;@PostConstructpublic 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(){@Overridepublic 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;}@Overrideprotected void writeInternal(String data, HttpOutputMessage outputMessage) throws IOException {//客户端加密strtry {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);@Autowiredprivate SecurityServerRestTemplate template;@Autowiredprivate 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@Lazypublic ClientSecurityConfig clientSecurityConfig(){return new ClientSecurityConfig();}@Bean//@DependsOn({"clientSecurityConfig"})@Lazypublic ClientDataResolver clientDataResolver(){return new ClientDataResolver();}@Bean//@DependsOn({"clientSecurityConfig"})@Lazypublic SecurityClientRestTemplate securityClientRestTemplate(){return new SecurityClientRestTemplate();}@Lazy@Primary@Beanpublic 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...     }

加密

    @Autowiredprivate ClientDataResolver clientDataResolver;....clientDataResolver.resolveSecurityFields(vo);....

服务端实例化相关组件

@Configurationpublic class SecurityConfig {/*数据加密相关组件*/@Bean//@DependsOn({"serverSecurityConfig"})@Lazypublic SecurityServerRestTemplate securityTemplate(){return new SecurityServerRestTemplate();}@Bean@Primarypublic RestTemplate restTemplate(){return new RestTemplate();}@Bean@Lazypublic ServerDataResolver resolver(){return new ServerDataResolver();}@Lazy@Beanpublic ServerSecurityConfig serverSecurityConfig(){return new ServerSecurityConfig();}}

服务端解密客户端加密的数据

    @Autowiredprivate 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);}}
上一篇:扩展KMP模板


下一篇:Try-Catch无法正确定位异常位置,我推荐2个有效技巧