業務場景
工作中使用mqtt做推送,activemq作爲broker
問題
(1)認證。客戶端連接broker需要認證
(2)主題權限。客戶只能訂閱自己有權限的主題
方案
1 認證
基本原理是activemq支持自定義插件。
公司系統是前後分離的,我們用戶在登錄後,會將token存與redis。這個認證也是基於redis來認證。
直接上代碼:maven項目
1.1 依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wyl</groupId>
<artifactId>broker-filter</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!-- activemq -->
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>activemq-broker</artifactId>
<version>5.15.8</version>
</dependency>
<!-- jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
</project>
1.2 插件入口類
注意,activemq支持注入的。詳見官方文檔:http://activemq.apache.org/developing-plugins.html。所以這裏的jedisPool,可以在配置文件中注入
package com.yiwo.plugin;
import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerPlugin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPool;
/**
* broker 過濾器
*
* @author wyl
* @since 2018-12-5
*/
public class BrokerFilter implements BrokerPlugin {
private Logger logger = LoggerFactory.getLogger(BrokerFilter.class);
private JedisPool jedisPool;
public Broker installPlugin(Broker broker) throws Exception {
logger.info("=============plugin install");
return new AuthFilter(broker, jedisPool);
}
public JedisPool getJedisPool() {
return jedisPool;
}
public void setJedisPool(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
}
1.3 認證過濾類
package com.yiwo.plugin;
import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerFilter;
import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.broker.region.Subscription;
import org.apache.activemq.command.ConnectionInfo;
import org.apache.activemq.command.ConsumerInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.HashMap;
import java.util.Map;
/**
* broker過濾器
*
* @author wyl
* @since 2018-12-6
*/
public class AuthFilter extends BrokerFilter {
private Logger logger = LoggerFactory.getLogger(AuthFilter.class);
private JedisPool jedisPool;
public AuthFilter(Broker next, JedisPool jedisPool) {
super(next);
this.jedisPool = jedisPool;
logger.info("=============install authFilter");
}
@Override
public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
long start = System.currentTimeMillis();
logger.info("=============addConnection");
logger.info("=============username=" + info.getUserName());
logger.info("=============password=" + info.getPassword());
auth(info.getUserName(), info.getPassword());
long end = System.currentTimeMillis();
logger.info("=============total time=" + (end - start));
super.addConnection(context, info);
}
/**
* 認證
*
* @param userName 前綴+用戶id。如 user:123
* @param password 登錄token。如 48fe6bddc509444c8773760427c52c96
* @return
* @author wyl
* @since 2018-12-6
*/
private void auth(String userName, String password) {
if (userName == null || password == null) {
throw new SecurityException("Invalid userName or password!");
}
String token = getUserRealToken(userName);
if (!token.equals(password)) {
throw new SecurityException("Invalid userName or password!");
}
}
/**
* 獲取用戶的token
*
* @param username 前綴+用戶id。user:123
* @return
* @author wyl
* @since 2018-12-6
*/
private String getUserRealToken(String username) {
// String token = jedisPool.getResource().get(username);
Jedis resource = jedisPool.getResource();
String token = resource.get(username);
resource.close();
logger.info("=============token=" + token);
if (token == null) {
throw new SecurityException("Invalid userName or password!");
}
return token;
}
坑:
剛開始使用:
String token = jedisPool.getResource().get(username);
獲取token。
web端訂閱可以成功,但是刷新多次之後就會連不上,經過排查,日誌
logger.info("=============token=" + token);
一直打印不出來,說明進入方法getUserRealToken()後有問題,方法中最有可能出問題的就是
String token = jedisPool.getResource().get(username);
猜想是jedisPool鏈接釋放問題,改爲:
Jedis resource = jedisPool.getResource();
String token = resource.get(username);
resource.close();
問題解決
1.4 配置插件
由於我們引入了redis的依賴,所以要將其jar包加入activemq的lib目錄。
另外activemq-broker已經有了,所有只需要將jedis-2.9.0.jar 和 我們的broker-filter-1.0-SNAPSHOT.jar 上傳即可
修改配置文件:activemq/conf/activemq.xml
首先是jedispool,配置在<beans>標籤中
<bean id="jedispool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="host" value="192.168.0.110"></constructor-arg>
<constructor-arg name="port" value="6379"></constructor-arg>
</bean>
然後是我們的插件,要配置在<broker>中,並注入jedispool
<plugins>
<bean xmlns="http://www.springframework.org/schema/beans" id="brokerAuthFilter" class="com.yiwo.plugin.BrokerFilter">
<property name="jedisPool" ref="jedispool"></property>
</bean>
</plugins>
1.5 重啓測試
正確的用戶名密碼,連接成功
錯誤的用戶名密碼,連接失敗
2 主題權限
原理和認證相同,可以覆蓋addConsumer實現,這裏不再贅述了