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;
@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);
}
}
posted on 2019-07-18 10:20 coding-now 阅读(124) 评论(0) 编辑 收藏 举报
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步