博客有个好处,就是可以在涨人气的同时当个备忘录用。
最近有个需求,有一个远程的mongo数据库,里面有好几个集合,需要每天定时增量更新到本地数据库,同时要保证数据的安全性,防止中途被抓包暴露。
实现思路就是多数据源,设置定时器,主读从写,然后使用SSL加密。然而以前mongo用的很少,就是一个简单的CRUD功能,所以一开始就遇到很多坑。
一、SpringBoot配置多数据源
首先在pom中导入依赖
<!-- mongodb -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
然后在application.properties中配置相关数据源
spring.data.mongodb.primary.host=远程数据库主机
spring.data.mongodb.primary.port=远程数据库端口
spring.data.mongodb.primary.database=远程数据库名称
spring.data.mongodb.primary.username=账号
spring.data.mongodb.primary.password=密码
spring.data.mongodb.secondary.host=127.0.0.1
spring.data.mongodb.secondary.port=27017
spring.data.mongodb.secondary.database=本地数据库名称
spring.data.mongodb.secondary.username=账号
spring.data.mongodb.secondary.password=密码
之后创建一个数据源抽象类
/*
数据源抽象类
*/
@Data
public abstract class AbstractMongoConfig {
private String host, database, username, password;
private int port;
public MongoDbFactory secMongoDbFactory() {
String url = "mongodb://"+username+":"+password+"@"+host+":"+port+"/"+database;
MongoClientURI uri = new MongoClientURI(url);
return new SimpleMongoDbFactory(new MongoClient(uri),database);
}
public abstract MongoTemplate getMongoTemplate() throws Exception;
}
设置主从数据源,从application.properties中读取连接信息
/*
配置远程数据库数据源
*/
@Configuration
@ConfigurationProperties(prefix = "spring.data.mongodb.primary")
public class PrimaryMongoConfig extends AbstractMongoConfig{
@Primary
@Override
public @Bean(name = "primaryMongoTemplate") MongoTemplate getMongoTemplate(){
return new MongoTemplate(priMongoDbFactory());
}
}
/*
配置本地数据库数据源
*/
@Configuration
@ConfigurationProperties(prefix = "spring.data.mongodb.secondary")
public class SecondaryMongoConfig extends AbstractMongoConfig {
@Override
public @Bean(name = "secondaryMongoTemplate") MongoTemplate getMongoTemplate(){
return new MongoTemplate(secMongoDbFactory());
}
}
为了方式SpringBoot自动注入mongotemplate,还要在启动类上增加内容
@SpringBootApplication(exclude={MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) //排除自动注入mongoTemplate
二、使用多数据源
使用时,指明要使用哪个数据源就可以了,如要对远程数据库操作,就使用primaryMongoTemplate。
在对mongo进行操作的使用,也可以使用实体类映射,但是要加上@Document注解,如果属性名和字段名不同,那么要加上@Field注解,如
@Data
@Document("test") // 对应集合名
public class BackUpEntity {
@Field("update_time") // 对应字段名
private Long updateTime;
}
然而mongo中可能有很多的内嵌文档等,有些复杂的表很难建立实体类映射,但是mongo简单在,可以使用json类型数据进行操作,不需要考虑实体类构造,简化代码如下。
@Repository
public class CommonRepository {
@Autowired
@Qualifier(value = "primaryMongoTemplate")
private MongoTemplate primaryMongoTemplate;
@Autowired
@Qualifier(value = "secondaryMongoTemplate")
private MongoTemplate secondaryMongoTemplate;
/*
查询远程数据库数据
*/
public JSONArray findSecondaryAll(String collectionName){
JSONArray json = JSONArray.parseArray(JSON.toJSONString(primaryMongoTemplate.findAll(String.class,collectionName)));
return json;
}
/*
备份远程数据库数据到本地
*/
public String backUpMongo(List<Object> list, String collectionName){
secondaryMongoTemplate.insert(list,collectionName);
return "备份集合"+collectionName+"成功";
}
}
在本地数据库写入数据的时候要先将查询到的JSONArray数据转成List再传入
List<Object> list = JSONObject.parseArray(findJson.toJSONString(), Object.class);
再写入的时候遇到了一个坑,报错信息
Exception authenticating MongoCredential{mechanism=SCRAM-SHA-1, userName='root', source='ys_pbx_data_test', password=<hidden>, mechanismProperties=<hidden>};
这是因为本地安装mongo的时候,并没有生成对应的管理员用户,所以要对数据库先生成管理员,赋予权限,然后在配置文件中填入账号密码,没有的话就会报错。
之后编写service层和controller层,调用repository,传入collectionName就可以了。
三、配置MongoDB SSL
因为数据比较重要,所以要对其进行加密。mongo使用的是tcp传输,但是支持ssl加密。使用Wireshark进行抓包时,可以发现传输过程中的确都是明文。一开始理解错需求,使用了AES加密了接口返回数据,数据包还是明文的,不过也防止了查询接口直接暴露数据。
我的远程数据库是阿里云的,SSL配置可以参考:
官方文档:http://mongodb.github.io/mongo-java-driver/3.0/driver/reference/connecting/ssl/
https://www.alibabacloud.com/help/zh/doc-detail/89276.html
具体过程如下
第一步是改造远程数据库的数据源配置,新建一个配置类
@Configuration
public class MongoSSLConfig {
@Bean
public static MongoClient createNetworkMongoClient() {
MongoCredential credentials = getCredentials();
String host = "主机名";
int port = "端口";
List<ServerAddress> addrs = Collections.singletonList(new ServerAddress(host, port));
System.setProperty("javax.net.ssl.trustStore","证书路径,注意使用jks文件");
System.setProperty("javax.net.ssl.trustStorePassword","信任库密码");
MongoClientOptions options = MongoClientOptions.builder()
.sslEnabled(true) //开启SSL连接
.sslInvalidHostNameAllowed(true) //不检查证书域名
.build();
return new MongoClient(addrs, credentials, options);
}
private static MongoCredential getCredentials() {
String username = "用户名";
String database = "数据库名";
String pass = "密码";
char[] password = pass.toCharArray();
return MongoCredential.createCredential(username, database, password);
}
}
修改 数据源抽象类 配置远程数据库新连接
public MongoDbFactory priMongoDbFactory() {
MongoClient mongoClient = MongoSSLConfig.createNetworkMongoClient();
return new SimpleMongoDbFactory(mongoClient,database);
}
在primaryMongoConfig中调用新方法
public @Bean(name = "primaryMongoTemplate") MongoTemplate getMongoTemplate(){
return new MongoTemplate(priMongoDbFactory());
}
第二步就是生成远程数据库的信任证书并下载到本地。
第三步将证书入本机信赖库,具体操作可以看官方文档,并将jks证书路径填入上面的代码中,如果使用pem等格式证书可能报错。
最后重启项目,可以正常运作,再次抓包,发现明文数据已经被加密。
三、Spring Data Mongo升级
spring data mongo 升级至了3.0.3版本,很多东西都被弃用了,相关方面文档,这边修改上文代码,即可重新配置数据源
直接连接数据库
import lombok.Data;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
/*
* mongodb数据源抽象类
*/
@Data
public abstract class AbstractMongoConfig {
private String host;
private int port;
private String database;
private String username;
private String password;
public MongoDatabaseFactory mongoDatabaseFactory() {
String url = "mongodb://"+username+":"+password+"@"+host+":"+port+"/"+database;
return new SimpleMongoClientDatabaseFactory(url);
}
public abstract MongoTemplate getMongoTemplate() throws Exception;
}
配置SSL连接
public MongoDatabaseFactory mongoDatabaseFactory() {
MongoClient mongoClient = MongoSSLConfig.createNetworkMongoClient(host,database,username,password,port);
return new SimpleMongoClientDatabaseFactory(mongoClient,database);
}
同时需要改写MongoSSLConfig类
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.yeastar.dataplatform.analyze.util.CommonUtil;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
/**
* MongoSSL连接配置类
* @author: liaoyuan
**/
@Configuration
public class MongoSSLConfig {
public static MongoClient createNetworkMongoClient(String host, String database, String username, String password, int port){
// 传入证书
System.setProperty("javax.net.ssl.trustStore", "证书位置");
System.setProperty("javax.net.ssl.trustStorePassword","");
MongoCredential credential = getCredentials(username, database, password);
MongoClientSettings settings = MongoClientSettings.builder()
.credential(credential)
.applyToSslSettings(builder -> {
builder.enabled(true); // 开启ssl连接
builder.invalidHostNameAllowed(true); // 禁用主机名验证
})
.applyToClusterSettings(builder ->
builder.hosts(Arrays.asList(new ServerAddress(host, port))))
.build();
MongoClient mongoClient = MongoClients.create(settings);
return mongoClient;
}
private static MongoCredential getCredentials(String username, String database, String pass) {
char[] password = pass.toCharArray();
return MongoCredential.createCredential(username, database, password);
}
}