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();
    }

2 定义需要向服务端加密传输的对象

@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...     
}    

3 加密

    @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);
		}
	}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值