前言
由于以前使用mongo都是簡單的本地裸奔揩晴,根本沒有設(shè)置過用戶密碼,昨天收到運(yùn)維同事反饋說服務(wù)器被挖礦木馬攻擊了贪磺,嚇得趕緊在docker里面開啟認(rèn)證硫兰,但是設(shè)置完后問題來了,開發(fā)同事又說不能用配置文件如下
spring:
data:
mongodb:
host: 127.0.0.1
port: 27017
username: sunny
password: 123456
database: admin
看起來很ok但是在啟動boot的時候在會連接兩次(別問我為什么是兩次寒锚,boot源碼還沒有看多少)劫映,第一次是失敗了,但是第二次成功了刹前,但是讀取數(shù)據(jù)時候又用的是mongoTemplate
(第一次創(chuàng)建的)這個導(dǎo)致一直卡死泳赋,沒有辦法只能用uri
方式配置了,下面介紹boot怎么解析它
配置文件(yml)
spring:
data:
mongodb:
uri: mongodb://sunny:123456@127.0.0.1:27017/file
對應(yīng)的java代碼是
@ConfigurationProperties(prefix = "spring.data.mongodb")
public class MongoProperties {
public static final String DEFAULT_URI = "mongodb://localhost/test";
// 默認(rèn)uri=
public String determineUri() {
return (this.uri != null ? this.uri : DEFAULT_URI);
}
....
// 關(guān)鍵代碼 idea ctrl+g 169 就可以看到
public String getMongoClientDatabase() {
if (this.database != null) {
return this.database;
}
return new MongoClientURI(determineUri()).getDatabase();
}
}
// 讀取yml文件前綴為(spring.data.mongodb)
由源碼可以看出這里如果uri=null則使用DEFAULT_URI
喇喉,然后進(jìn)入MongoClientURI
類
public class MongoClientURI {
private final ConnectionString proxied;
private final MongoClientOptions.Builder builder;
/**
* Creates a MongoURI from the given string.
* 從給定的字符串創(chuàng)建一個MongoURI
* @param uri the URI
*/
public MongoClientURI(final String uri) {
this(uri, new MongoClientOptions.Builder());
}
/**
* Mongo database URI. Cannot be set with host, port and credentials.
*/
public MongoClientURI(final String uri, final MongoClientOptions.Builder builder) {
this.builder = notNull("builder", builder);
proxied = new ConnectionString(uri);
}
}
到這里了就先看new MongoClientOptions.Builder()
方法
@Immutable
public class MongoClientOptions {
/**
* Creates a Builder for MongoClientOptions, getting the appropriate system properties *for initialization.
* 這里MongoClientOptions創(chuàng)建一個構(gòu)建器祖今,并且設(shè)置一些系統(tǒng)屬性
*/
public Builder() {
/*設(shè)置心跳頻率,在集群中*/ heartbeatFrequency(Integer.parseInt(System.getProperty("com.mongodb.updaterIntervalMS", "10000")));
/*最小心跳頻率*/ minHeartbeatFrequency(Integer.parseInt(System.getProperty("com.mongodb.updaterIntervalNoMasterMS", "500")));
/*設(shè)置用于群集心跳的連接的連接超時*/ heartbeatConnectTimeout(Integer.parseInt(System.getProperty("com.mongodb.updaterConnectTimeoutMS", "20000")));
/*socket 鏈接超時*/ heartbeatSocketTimeout(Integer.parseInt(System.getProperty("com.mongodb.updaterSocketTimeoutMS", "20000")));
/*可接受的延遲差 15秒*/ localThreshold(Integer.parseInt(System.getProperty("com.mongodb.slaveAcceptableLatencyMS", "15")));
}
}
System.getProperty(xxxx)可以在jvm里面設(shè)置或者直接在boot啟動方法里面設(shè)置拣技。
看到這里后發(fā)現(xiàn)使用了一個proxied(代理)繼續(xù)往下看
public class ConnectionString {
/**
* Creates a ConnectionString from the given string.
*
* @param connectionString the connection string
* @since 3.0
*/
public ConnectionString(final String connectionString) {
this.connectionString = connectionString;
//這里可以看出支持兩種協(xié)議:mongodb://和mongodb+srv://
boolean isMongoDBProtocol = connectionString.startsWith(MONGODB_PREFIX);
boolean isSRVProtocol = connectionString.startsWith(MONGODB_SRV_PREFIX);
if (!isMongoDBProtocol && !isSRVProtocol) {
throw new IllegalArgumentException(format("The connection string is invalid. "
+ "Connection strings must start with either '%s' or '%s", MONGODB_PREFIX, MONGODB_SRV_PREFIX));
}
// 截取協(xié)議頭千诬,eg:sunny:123456@127.0.0.1:27007/file?connectTimeoutMS=400000
String unprocessedConnectionString;
if (isMongoDBProtocol) {
unprocessedConnectionString = connectionString.substring(MONGODB_PREFIX.length());
} else {
unprocessedConnectionString = connectionString.substring(MONGODB_SRV_PREFIX.length());
}
// 分割出用戶和主機(jī)信息 eg:sunny:123456@127.0.0.1:27007
String userAndHostInformation;
int idx = unprocessedConnectionString.lastIndexOf("/");
if (idx == -1) {
if (unprocessedConnectionString.contains("?")) {
throw new IllegalArgumentException("The connection string contains options without trailing slash");
}
userAndHostInformation = unprocessedConnectionString;
unprocessedConnectionString = "";
} else {
//userAndHostInformation=sunny:123456@127.0.0.1:27007
userAndHostInformation = unprocessedConnectionString.substring(0, idx);
// unprocessedConnectionString=file?connectTimeoutMS=400000
unprocessedConnectionString = unprocessedConnectionString.substring(idx + 1);
}
// Split the user and host information
String userInfo;
String hostIdentifier;
String userName = null;
char[] password = null;
idx = userAndHostInformation.lastIndexOf("@");
if (idx > 0) {
// userInfo=sunny:123456
userInfo = userAndHostInformation.substring(0, idx);
// hostIdentifier=127.0.0.1:27007
hostIdentifier = userAndHostInformation.substring(idx + 1);
// 判斷userInfo是否包含@和:出現(xiàn)次數(shù)大于1
int colonCount = countOccurrences(userInfo, ":");
if (userInfo.contains("@") || colonCount > 1) {
throw new IllegalArgumentException("The connection string contains invalid user information. "
+ "If the username or password contains a colon (:) or an at-sign (@) then it must be urlencoded");
}
if (colonCount == 0) {
userName = urldecode(userInfo);
} else {
// idx=5
idx = userInfo.indexOf(":");
// userName=sunny
userName = urldecode(userInfo.substring(0, idx));
//passpword=123456
password = urldecode(userInfo.substring(idx + 1), true).toCharArray();
}
} else {
hostIdentifier = userAndHostInformation;
}
// 驗(yàn)證host
List<String> unresolvedHosts = unmodifiableList(parseHosts(asList(hostIdentifier.split(",")), isSRVProtocol));
this.hosts = isSRVProtocol ? resolveHostFromSrvRecords(unresolvedHosts.get(0)) : unresolvedHosts;
// 解析參數(shù)
String nsPart;
idx = unprocessedConnectionString.indexOf("?");
if (idx == -1) {
nsPart = unprocessedConnectionString;
unprocessedConnectionString = "";
} else {
// nsPart=file 獲取數(shù)據(jù)庫名稱
nsPart = unprocessedConnectionString.substring(0, idx);
// unprocessedConnectionString=connectTimeoutMS=400000
unprocessedConnectionString = unprocessedConnectionString.substring(idx + 1);
}
if (nsPart.length() > 0) {
nsPart = urldecode(nsPart);
idx = nsPart.indexOf(".");
// 是否指定集合
if (idx < 0) {
database = nsPart;
collection = null;
} else {
database = nsPart.substring(0, idx);
collection = nsPart.substring(idx + 1);
}
} else {
database = null;
collection = null;
}
/*數(shù)據(jù)庫健康鏈接 mongodb+srv 協(xié)議才使用*/
String txtRecordsQueryParameters = isSRVProtocol ? resolveAdditionalQueryParametersFromTxtRecords(unresolvedHosts.get(0)) : "";
/*鏈接參數(shù) connectTimeoutMS=400000*/
String connectionStringQueryParamenters = unprocessedConnectionString;
Map<String, List<String>> connectionStringOptionsMap = parseOptions(connectionStringQueryParamenters);
Map<String, List<String>> txtRecordsOptionsMap = parseOptions(txtRecordsQueryParameters);
//判斷是否設(shè)置 authsource、replicaset
if (!ALLOWED_OPTIONS_IN_TXT_RECORD.containsAll(txtRecordsOptionsMap.keySet())) {
throw new MongoConfigurationException(format("A TXT record is only permitted to contain the keys %s, but the TXT record for "
+ "'%s' contains the keys %s", ALLOWED_OPTIONS_IN_TXT_RECORD, unresolvedHosts.get(0), txtRecordsOptionsMap.keySet()));
}
Map<String, List<String>> combinedOptionsMaps = combineOptionsMaps(txtRecordsOptionsMap, connectionStringOptionsMap);
if (isSRVProtocol && !combinedOptionsMaps.containsKey("ssl")) {
combinedOptionsMaps.put("ssl", singletonList("true"));
}
translateOptions(combinedOptionsMaps);
credential = createCredentials(combinedOptionsMaps, userName, password);
warnOnUnsupportedOptions(combinedOptionsMaps);
}
}
完結(jié)
mongo這塊代碼是臨時看的膏斤,可能沒有太認(rèn)真徐绑,如果有誤請指出,還有就是mongodb+srv 協(xié)議沒有使用過莫辨,有使用經(jīng)驗(yàn)的歡迎留言傲茄,謝謝