mqtt是目前主流iot通信协议,之前用过EMQ,使用erlang写的,我表示看不懂。使用插件鉴权基本可以满足需求。但是不够灵活。最近我发现了JAVA版的mqtt服务器 moquette 底层使用netty实现。非常的轻。
今天我来阅读一下moquette的源代码,以便于使用比较灵活的鉴权方式。
moquette的官方文档没有EMQ详细,好在看得懂Java代码。
首先找到服务器main入口 位于 io.moquette.broker.Server 类
public static void main(String[] args) throws IOException {
final Server server = new Server();
server.startServer();
System.out.println("Server started, version 0.12.1-SNAPSHOT"); //Bind a shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(server::stopServer));
}
实际上 new Server的时候去读了一些配置,
#*********************************************************************
# Optional Database Authentication
#*********************************************************************
# authenticator_class io.moquette.broker.security.DBAuthenticator
# authenticator.db.driver org.postgresql.Driver# authenticator.db.url jdbc:postgresql://localhost/test?user=dbuser&password=dbpassword
# authenticator.db.query SELECT PASSWORD FROM ACCOUNT WHERE LOGIN=?# authenticator.db.digest SHA-256
他的配置里面有这样一段示例配置,这就很明显了,我们果断进DBAuthenticator类里面,发现它实现了接口IAuthenticator。这就是一种可插拔的设计方式,如果我们需要自己鉴权,实现这个接口,然后放到配置文件里面就行了,他会通过反射去实例化这个类的。
public interface IAuthenticator {
boolean checkValid(String clientId, String username, byte[] password);
}
但是从这个接口上看,传入参数少了一点东西,因为从我的需求上看,有的用户是只允许特定ip登录的,但是这个接口里面并没有提供ip。没关系我们可以打断点往上看。于是我就在DBAuthenticator.checkValid打了一个断点
java.lang.RuntimeException
at io.moquette.broker.security.DBAuthenticator.checkValid(DBAuthenticator.java:119)
at io.moquette.broker.MQTTConnection.login(MQTTConnection.java:235)
at io.moquette.broker.MQTTConnection.processConnect(MQTTConnection.java:162)
at io.moquette.broker.MQTTConnection.handleMessage(MQTTConnection.java:67)
at io.moquette.broker.NewNettyMQTTHandler.channelRead(NewNettyMQTTHandler.java:63)
这是调用栈,为了方便复制,我给他抛了一个异常。
然后就一层一层的往下看,发现
public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception {
这次调用中有传入一个 ctx , 这是netty api给我们提供的一个对象 ,从这个对象里面我们就能拿到ip了
SocketAddress socketAddress = ctx.pipeline().channel().remoteAddress();
mqttConnection.handleMessage(msg,socketAddress);
然后我再一层一层传下去,最后把接口改一下,就可以在接口里面拿到ip了。
这只是连接时处理的,实际上对ACL的控制,也有同样的一个接口。
public class PermitAllAuthorizatorPolicy implements IAuthorizatorPolicy {
@Override
public boolean canWrite(Topic topic, String user, String client) {
return true;
}
@Override
public boolean canRead(Topic topic, String user, String client) {
return true;
}
}
拿ip的方式也是一样的,这里就不在赘述了。
总结:通过对IAuthenticator,IAuthorizatorPolicy两个接口进行实现,我们很容易可以做到自定义鉴权