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);
		}
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章