Kafka安全机制解析及重构(二)| PLAIN机制解读

上一篇Kafka安全机制解析及重构(一)中介绍了Kafka的安全认证的流程。其实Kafka官方也是推荐用户自己写一个安全认证模块的。官方在介绍SASL/PLAIN模式的时候是这样说的

  • SASL/PLAIN should be used only with SSL as transport layer to ensure that clear passwords are not transmitted on the wire without encryption.
    SASL/PLAIN模式应该搭配SSL协议使用,这样可以避免密码明文传输
  • The default implementation of SASL/PLAIN in Kafka specifies usernames and passwords in the JAAS configuration file as shown here. To avoid storing passwords on disk, you can plug in your own implementation of javax.security.auth.spi.LoginModule that provides usernames and passwords from an external source. The login module implementation should provide username as the public credential and password as the private credential of the Subject. The default implementation org.apache.kafka.common.security.plain.PlainLoginModule can be used as an example.
    由于SASL/PLAIN模式中密码是明文保存在JAAS配置文件中的,为了避免用户密码的明文保存,用户可以自己实现LoginModule接口来从别的位置获取用户密码。用户自己实现的login module需要将用户名和密码存放在Subject对象中,Kafka的PlainLoginModule可以作为一个示例供参考
  • In production systems, external authentication servers may implement password authentication. Kafka brokers can be integrated with these servers by adding your own implementation of javax.security.sasl.SaslServer. The default implementation included in Kafka in the packageorg.apache.kafka.common.security.plain can be used as an example to get started.
    在生产环境中,Kafka可以从外部的源获取用户密码信息,用户可以通过实现SaslServer接口来实现这个能力,plain模式的源码同样可以作为参考。

通过阅读PLAIN模式的源码,的确让我理清了JAAS的认证模式,自己实现的KAFKA的认证模块需要实现以下几个接口

  1. LoginModule,接口类是javax.security.auth.spi.LoginModule,这个类会在启动时被自动实例化。
  2. Provider,接口类是java.security.Provider,这个类一般在LoginModule实例化的时候被调起,将SaslClient和SaslServer的构造方法告知Sasl模块。一般在客户端写一个SaslClientProvider,把SaslClientFactory传入即可,而在Server端则需要写SaslClientProvider和SaslServerProvider,传入SaslClientFactory和SaslServerFactory。
  3. SaslClientFactory,接口类是javax.security.sasl.SaslClientFactory,在这个工厂类中构造出SaslClient。
  4. SaslServerFactory,接口类是javax.security.sasl.SaslServerFactory,在这个工厂类中构造出SaslServer。
  5. SaslClient,由工厂类SaslClientFactory生成,用于处理服务端请求,构造鉴权报文并发送。
  6. SaslServer,由工厂类SaslServerFactory生成,用于处理客户端请求,并进行用户密码认证。

这里我们同样把上一章中的流程图放出来,并增加一些更细节的说明,下面的源码分析可以就着这个图看。

鉴权机制流程

我们先看看PLAIN模式的代码来热热身,源码我稍微缩减了一下,实际自己实现的时候最好别照搬,先实现出接口,然后看看有哪些方法是必须实现的。

public class PlainLoginModule implements LoginModule {

    private static final String USERNAME_CONFIG = "username";
    private static final String PASSWORD_CONFIG = "password";

    static {
        /**
        / 这里初始化PlainServerProvider,PLAIN模式没有ClientProvider,
        / 因为不需要进行客户端信息加密的工作,直接传明文。
        **/
        PlainSaslServerProvider.initialize();
    }

    @Override
    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
        // 从options里获取用户密码,option是从JAAS配置文件中读取的信息
        String username = (String) options.get(USERNAME_CONFIG);
        if (username != null)
            subject.getPublicCredentials().add(username);
        String password = (String) options.get(PASSWORD_CONFIG);
        if (password != null)
            subject.getPrivateCredentials().add(password);
    }
}

PlainLoginModule这个类的作用是:

  • 获取用户密码并将其放入Subject类中
  • 初始化Provider类。

再来看Server的Provider类,这个类比较简单,是把安全机制及工厂类传入Security对象,供后续如果有需要就通过反射机制由该工厂类生成对应的PlainServer。

public class PlainSaslServerProvider extends Provider {

    private static final long serialVersionUID = 1L;

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

    public static void initialize() {
        Security.addProvider(new PlainSaslServerProvider());
    }
}

PlainSaslServerFactory类是用来构造PlainSaslServer的工厂类,也是比较简单的,PlainSaslServerFactory类在PlainSaslServer类中,实际如果自己要写的话最好分出一个新的类,不然构造的时候会出问题。SaslServerFactory接口必须实现两个方法:

  • createSaslServer,首要是构造SaslServer,不然怎么叫工厂类呢
  • getMechanismNames,上层需要知道这个工厂类对应的机制,比如PLAIN,SCRAM-SHA-256等等,如果自己写的话,像阿里的认证机制叫ONS,华为的叫DMS。
    public static class PlainSaslServerFactory implements SaslServerFactory {

        @Override
        public SaslServer createSaslServer(String mechanism, String protocol, String serverName, Map<String, ?> props, CallbackHandler cbh)
            throws SaslException {

            if (!PLAIN_MECHANISM.equals(mechanism)) {
                throw new SaslException(String.format("Mechanism \'%s\' is not supported. Only PLAIN is supported.", mechanism));
            }
            return new PlainSaslServer(cbh);
        }

        @Override
        public String[] getMechanismNames(Map<String, ?> props) {
            String noPlainText = (String) props.get(Sasl.POLICY_NOPLAINTEXT);
            if ("true".equals(noPlainText))
                return new String[]{};
            else
                return new String[]{PLAIN_MECHANISM};
        }
    }

重头戏是PlainSaslServer类,主要的方法就是evaluateResponse,这个方法被用于处理客户端发送过来的报文,在PLAIN模式中,报文被一个空字节切分成authorizationID,username,password三个元素。然后通过JaasUtils.defaultServerJaasConfigOption(JAAS_USER_PREFIX + username, PlainLoginModule.class.getName())方法来获取到JAAS配置文件中用户名对应的密码,比对通过则返回一个空字节报文至客户端。

public class PlainSaslServer implements SaslServer {

    public static final String PLAIN_MECHANISM = "PLAIN";
    private static final String JAAS_USER_PREFIX = "user_";

    private boolean complete;
    private String authorizationID;

    public PlainSaslServer(CallbackHandler callbackHandler) {
    }

    @Override
    public byte[] evaluateResponse(byte[] response) throws SaslException {
        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 {
            // 从配置文件中获取到expectedPassword,而后比对。
            String expectedPassword = JaasUtils.defaultServerJaasConfigOption(JAAS_USER_PREFIX + username, PlainLoginModule.class.getName());
            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);
        }
        complete = true;
        return new byte[0];
    }
}

我们可以发现PLAIN机制下的的SaslServer中并没有处理第3步new byte[0]的步骤,也没有第4步Server返回server token的步骤,而是直接进入了第5步发送用户权限信息,校验通过则返回空字节。这是因为在client里有个hasInitialResponse()方法,如果在自己实现的SaslClient中将这个方法的返回置为true,就直接进入3,4步。如果不跳过的话,SaslServer的evaluateResponse()方法就必须实现处理空字节客户端报文的逻辑(华为的客户端代码就这么干了)。我在自己重构的时候调试了半天,仔细读完代码才知道有这段逻辑在。

今天这篇介绍了一下kafka认证的关键类,解析了一下PLAIN模式,根据这些知识我们就可以实现一个自己的安全机制了,下一章中就介绍重构时候需要注意的几个要点吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270