kafka 0.10.1.0 權限驗證源碼分析 原

初始化流程圖

ChannelBuilders.create創建ChannelBuilder對應關係如下:

switch (securityProtocol) {
    case SSL:
        requireNonNullMode(mode, securityProtocol);
        channelBuilder = new SslChannelBuilder(mode);
        break;
    case SASL_SSL:
    case SASL_PLAINTEXT:
        requireNonNullMode(mode, securityProtocol);
        if (loginType == null)
            throw new IllegalArgumentException("`loginType` must be non-null if `securityProtocol` is `" + securityProtocol + "`");
        if (mode == Mode.CLIENT && clientSaslMechanism == null)
            throw new IllegalArgumentException("`clientSaslMechanism` must be non-null in client mode if `securityProtocol` is `" + securityProtocol + "`");
        channelBuilder = new SaslChannelBuilder(mode, loginType, securityProtocol, clientSaslMechanism, saslHandshakeRequestEnable);
        break;
    case PLAINTEXT:
    case TRACE:
        channelBuilder = new PlaintextChannelBuilder();
        break;
    default:
        throw new IllegalArgumentException("Unexpected securityProtocol " + securityProtocol);
}


這裏我們配置的listeners值是
listeners=SASL_PLAINTEXT://0.0.0.0:9092
故解析出來的protocol就是SASL_PLAINTEXT,相應的ChannelBuilder也就是SaslChannelBuilder
到這裏,權限驗證相關的組件算是構建完畢了,然後我們看當一個連接接進來的時候,這些組件是怎麼工作的。
入口是Accepter.accept

接下來是Processor線程

+ SaslChannelBuilder.buildChannel:
    * 這裏會根據構造SaslChannelBuilder時傳進來的mode參數的不同選擇構造SaslServerAuthenticator還是SaslClientAuthenticator,我們這裏是服務端,當然是構造SaslServerAuthenticator.
    * 構造好SaslServerAuthenticator後會調用它的configure函數,進行一些初始化配置。
    * 把SaslServerAuthenticator對象作爲參數傳給KafkaChannel返回。
+ 這裏註冊好新的kafkaChannel後會調用poll函數完成一些IO的讀寫操作,而權限驗證的處理就以這裏爲入口。
+ pollSelectionKeys函數會處理所有可完成連接,可讀或可寫的KafkaChannel。而權限驗證部分則出現在KafkaChannel的準備階段。
+ authenticate這個函數內會根據目前握手所處的狀態的不同而做不同的處理,
    * 首先是HANDSHAKE_REQUEST狀態,調用handleKafkaRequest處理第一階段的握手請求,解析出客戶端發來的mechanism,
    根據mechanism創建SaslServer,代碼如下:

saslServer = Subject.doAs(subject, new PrivilegedExceptionAction<SaslServer>() {
    public SaslServer run() throws SaslException {
        return Sasl.createSaslServer(saslMechanism, "kafka", host, configs, callbackHandler);
    }
});


這裏具體根據我們在jaas配置文件中的配置,最後是返回了一個PlainSaslServer,具體爲什麼返回了一個PlainSaslServer稍後講。
* 然後下一狀態是AUTHENTICATE,驗證客戶端發來的明文用戶名和密碼,調用了PlainSaslServer的evaluateResponse,代碼如下

String[] tokens;
try {
    tokens = new String(response, "UTF-8").split("\u0000");
} catch (UnsupportedEncodingException e) {
    throw new SaslException("UTF-8 encoding not supported", e);
}
if (tokens.length != 3)
    throw new SaslException("Invalid SASL/PLAIN response: expected 3 tokens, got " + tokens.length);
authorizationID = tokens[0];
String username = tokens[1];
String password = tokens[2];

if (username.isEmpty()) {
    throw new SaslException("Authentication failed: username not specified");
}
if (password.isEmpty()) {
    throw new SaslException("Authentication failed: password not specified");
}
if (authorizationID.isEmpty())
    authorizationID = username;
try {
    String expectedPassword = JaasUtils.jaasConfig(LoginType.SERVER.contextName(), JAAS_USER_PREFIX + username);
    if (!password.equals(expectedPassword)) {
        throw new SaslException("Authentication failed: Invalid username or password");
    }
} catch (IOException e) {
    throw new SaslException("Authentication failed: Invalid JAAS configuration", e);
}


所以這裏可以根據我們的需求根據客戶端傳過來的username和password動態的去某一個數據源獲取和匹配其合法性。

jaas配置文件:

KafkaServer {
    org.apache.kafka.common.security.plain.PlainLoginModule required
    username="xxxxx"
    password="yyyyy"
    user_xxxxx="yyyyy";
};


看到這裏我們配置了一個org.apache.kafka.common.security.plain.PlainLoginModule

上面講到返回了一個PlainSaslServer,具體是怎麼返回的呢?我們需要從SaslChannelBuilder的configure說起,上流程圖:
+ LoginContext.init這裏會初始化ConfigFile,讀取jaas配置文件中的內容
+ LoginContext.login這裏會實例化jaas配置文件中的PlainLoginModule,並依次調用其initialize和login函數,
其中initialize函數會把username和password配置到subject裏面去,這個subject最終會通過LoginManager的subject函數在SaslChannelBuilder的buildChannel函數中獲取到,設置到SaslClientAuthenticator中用於和其他服務器通訊驗證使用。
+ 而PlainLoginModule可不僅僅只做了這一件事情,該類定義了一個靜態塊初始化代碼,調用了PlainSaslServerProvider的initialize函數用於註冊創建負責做PLAIN協議驗證的類的工廠類PlainSaslServerFactory。代碼如下:

protected PlainSaslServerProvider() {
    super("Simple SASL/PLAIN Server Provider", 1.0, "Simple SASL/PLAIN Server Provider for Kafka");
    super.put("SaslServerFactory." + PlainSaslServer.PLAIN_MECHANISM, PlainSaslServerFactory.class.getName());
}


我們看到註冊的是一個SaslServerFactory.PLAIN -> org.apache.kafka.common.security.plain.PlainSaslServer.PlainSaslServerFactory的對應關係
而再看前面調用的Sasl的createSaslServer的代碼:

String mechFilter = "SaslServerFactory." + mechanism;
Provider[] provs = Security.getProviders(mechFilter);
for (int j = 0; provs != null && j < provs.length; j++) {
    className = provs[j].getProperty(mechFilter);
    if (className == null) {
        throw new SaslException("Provider does not support " +
            mechFilter);
    }
    fac = (SaslServerFactory) loadFactory(provs[j], className);
    if (fac != null) {
        mech = fac.createSaslServer(
            mechanism, protocol, serverName, props, cbh);
        if (mech != null) {
            return mech;
        }
    }
}


其中mechFilter的值即是SaslServerFactory.PLAIN,這裏取到PlainSaslServerFactory然後調用createSaslServer方法返回了一個PlainSaslServer

綜上,kafka內部的整個權限驗證的初始化流程和驗證邏輯已經比較清晰了(可能講的比較亂,反正我是清晰了),但是我們發現,跟網上的其他自己編寫LoginModule模塊做驗證的方式不同,kafka的PlainLoginModule這個類本身並沒有做什麼跟驗證有關的邏輯,

只是做了一些初始化和註冊provider的工作,而真正做權限驗證的是從provider間接生產出來的PlainSaslServer類。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章