Activemq 自定义插件开发

414 阅读2分钟

Activemq 通过开发自定义插件连接 MySql, 登录时查询数据库用户信息实现登录认证授权

自定义插件开发分为六步:

1. 创建 UserAuthPlugin 类实现 BrokerPlugin 接口, 重写 installPlugin(Broker broker) 方法:

package com.xy.auth;

import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerPlugin;

import java.util.List;

public class UserAuthPlugin implements BrokerPlugin {
	private static final String TAG = " ------  UserAuthPlugin  ";

	List<String> configs;

	public Broker installPlugin(Broker broker) throws Exception {
		System.out.println(TAG + "installPlugin(Broker broker)");
		
		GoAuth.INSTANCE.AuthPluginInit("init", 2, "relases v2");
		
		return new MqttAuthorizationBroker(broker, configs);
	}

	public List<String> getConfigs() {
		return configs;
	}

	public void setConfigs(List<String> configs) {
		this.configs = configs;
	}

}

2. 创建 MqttAuthorizationBroker 类,继承 BrokerFilter , 重写 addConnection(ConnectionContext context, ConnectionInfo info) 方法中实现登录认证, 在 addConsumeraddProducer 方法中实现可读和可写授权

package com.xy.auth;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.BrokerFilter;
import org.apache.activemq.broker.Connection;
import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.broker.ProducerBrokerExchange;
import org.apache.activemq.broker.region.CompositeDestinationInterceptor;
import org.apache.activemq.broker.region.Destination;
import org.apache.activemq.broker.region.DestinationInterceptor;
import org.apache.activemq.broker.region.RegionBroker;
import org.apache.activemq.broker.region.Subscription;
import org.apache.activemq.command.ActiveMQDestination;
import org.apache.activemq.command.BrokerInfo;
import org.apache.activemq.command.ConnectionInfo;
import org.apache.activemq.command.ConsumerInfo;
import org.apache.activemq.command.DestinationInfo;
import org.apache.activemq.command.Message;
import org.apache.activemq.command.ProducerInfo;
import org.apache.activemq.security.SecurityContext;

/**
 * 认证授权接口实现
 * 
 * @author Administrator
 *
 */
public class MqttAuthorizationBroker extends BrokerFilter {
	private static final String TAG = " ------  MqttAuthorizationBroker  ";

	List<String> configs;

	public MqttAuthorizationBroker(Broker next, List<String> configs) {
		super(next);
		this.configs = configs;

		final RegionBroker regionBroker = (RegionBroker) next.getAdaptor(RegionBroker.class);
		final CompositeDestinationInterceptor compositeInterceptor = (CompositeDestinationInterceptor) regionBroker
				.getDestinationInterceptor();
		DestinationInterceptor[] interceptors = compositeInterceptor.getInterceptors();
		interceptors = Arrays.copyOf(interceptors, interceptors.length + 1);
		interceptors[interceptors.length - 1] = new MqttAuthorizationDestinationInterceptor(this);
		compositeInterceptor.setInterceptors(interceptors);
	}

	/**
	 * 用户名密码认证
	 * 
	 * @param clientId
	 * @param userName
	 * @param passWord
	 * @return
	 */
	private boolean isValid(String clientId, String userName, String passWord) {
		int isValid = GoAuth.INSTANCE.AuthUnpwdCheck(userName, passWord, clientId);
		return 1 == isValid;
	}

	/**
	 * 授权
	 * 
	 * @param destination
	 * @param clientid
	 * @param username
	 * @param topic
	 * @param acc         1: subscribe, 2: publish, 3: pubsub
	 * @return
	 */
	protected boolean checkDestinationAdmin(ActiveMQDestination destination, String clientid, String username,
			String topic, int acc) {
		System.out.println(TAG + "checkDestinationAdmin clientid: " + clientid + "  username: " + username + "  topic: "
				+ topic + "  acc: " + acc);

		int isAuth = GoAuth.INSTANCE.AuthAclCheck(clientid, username, topic, acc);

		return 1 == isAuth;
	}

	@Override
	public void addBroker(Connection connection, BrokerInfo info) {
		System.out.println(TAG + "addBroker(Connection connection, BrokerInfo info)");
		super.addBroker(connection, info);
	}

	@Override
	public void brokerServiceStarted() {
		System.out.println(TAG + "brokerServiceStarted()");
		super.brokerServiceStarted();
	}

	/**
	 * 由于验证需要在创建连接时进行,因此重写BrokerFilter的addConnection方法
	 */
	@Override
	public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
		System.out.println(TAG + "addConnection(ConnectionContext context, ConnectionInfo info)");
		if (isValid(info.getClientId(), info.getUserName(), info.getPassword())) {
			super.addConnection(context, info);
		} else {
			System.out
					.println(TAG + "is Auth fail username: " + info.getUserName() + " password: " + info.getPassword());
			throw new SecurityException("Connection faild username or password error");
		}
	}

	/**
	 * //由于需要授权是否可读消息,因此重写BrokerFilter的 addConsumer
	 * 方法,在该方法中,从访问控制列表中查看是否具有读授权,并调用next引用的addConsumer方法
	 */
	@Override
	public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
		System.out.println(TAG + "addConsumer(ConnectionContext context, ConsumerInfo info)");

		ActiveMQDestination destination = info.getDestination();
		if (destination != null) {
			String msgType = destination.getDestinationTypeAsString();
			String topic = destination.toString();

			System.out.println(TAG + "addConsumer msgType: " + msgType + "  topic: " + topic);

			Map<String, String> opts = destination.getOptions();
			if (null != opts) {
				for (Map.Entry<String, String> entry : opts.entrySet()) {
					System.out.println(TAG + "key: " + entry.getKey() + "  value: " + entry.getValue());
				}
			}

			if ("Topic".equals(msgType)) {
				if (!topic.startsWith("topic://ActiveMQ.Advisory") && !topic.startsWith("topic://$SYS")
						&& !topic.equals("topic://")) {
					if (!checkDestinationAdmin(destination, context.getClientId(), context.getUserName(), topic, 1)) {
						throw new SecurityException(
								"User " + context.getUserName() + " is not authorized to read from: " + destination);
					}
				}
			}

		}

		return super.addConsumer(context, info);
	}

	/**
	 * 由于需要授权是否可写消息,因此重写BrokerFilter的 addProducer
	 * 方法,在该方法中,从访问控制列表中查看是否具有写授权,并调用next引用的 addProducer 方法
	 */
	@Override
	public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception {
		System.out.println(TAG + "addProducer(ConnectionContext context, ProducerInfo info)");

		ActiveMQDestination destination = info.getDestination();
		if (destination != null) {
			String msgType = destination.getDestinationTypeAsString();
			String topic = destination.toString();
			System.out.println(TAG + "addProducer msgType: " + msgType + "  topic: " + topic);

			Map<String, String> opts = destination.getOptions();
			if (null != opts) {
				for (Map.Entry<String, String> entry : opts.entrySet()) {
					System.out.println(TAG + "key: " + entry.getKey() + "  value: " + entry.getValue());
				}
			}

			if ("Topic".equals(msgType)) {
				if (!topic.startsWith("topic://ActiveMQ.Advisory") && !topic.startsWith("topic://$SYS")
						&& !topic.equals("topic://")) {
					if (!checkDestinationAdmin(destination, context.getClientId(), context.getUserName(), topic, 2)) {
						throw new SecurityException(
								"User " + context.getUserName() + " is not authorized to write to: " + destination);
					}
				}
			}
		}

		super.addProducer(context, info);
	}

	@Override
	public void send(ProducerBrokerExchange producerExchange, Message messageSend) throws Exception {
		System.out.println(TAG + "send(ProducerBrokerExchange producerExchange, Message messageSend)");

		ActiveMQDestination destination = messageSend.getDestination();

		if (destination != null) {
			String msgType = destination.getDestinationTypeAsString();
			String topic = destination.toString();

			System.out.println(TAG + "send msgType: " + msgType + "  topic: " + topic);

			Map<String, String> opts = destination.getOptions();
			if (null != opts) {
				for (Map.Entry<String, String> entry : opts.entrySet()) {
					System.out.println(TAG + "key: " + entry.getKey() + "  value: " + entry.getValue());
				}
			}

			if ("Topic".equals(msgType)) {
				if (!topic.startsWith("topic://ActiveMQ.Advisory") && !topic.startsWith("topic://$SYS")
						&& !topic.equals("topic://")) {

					if (!checkDestinationAdmin(destination, producerExchange.getConnectionContext().getClientId(),
							producerExchange.getConnectionContext().getUserName(), topic, 2)) {
						throw new SecurityException("User " + producerExchange.getConnectionContext().getUserName()
								+ " is not authorized to write to: " + destination);
					}
				}
			}
		}

		super.send(producerExchange, messageSend);
	}
}

3. 创建 MqttAuthorizationDestinationInterceptor 类实现 DestinationInterceptor 接口,在 intercept(Destination destination) 生成一个订阅的 DestinationFilter

package com.xy.auth;

import org.apache.activemq.broker.Broker;
import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.broker.region.Destination;
import org.apache.activemq.broker.region.DestinationInterceptor;
import org.apache.activemq.command.ActiveMQDestination;

public class MqttAuthorizationDestinationInterceptor implements DestinationInterceptor{

	private final MqttAuthorizationBroker broker;
	
	public  MqttAuthorizationDestinationInterceptor(MqttAuthorizationBroker broker) {
		this.broker = broker;
	}
	
	
	@Override
	public Destination intercept(Destination destination) {
		return new MqttAuthorizationDestinationFilter(destination, broker);
	}

	@Override
	public void remove(Destination destination) {}

	@Override
	public void create(Broker broker, ConnectionContext context, ActiveMQDestination destination) throws Exception {}

}

4. 创建 MqttAuthorizationDestinationFilter 类继承 DestinationFilter 接口,在 addSubscription 判断订阅的权限:

package com.xy.auth;

import org.apache.activemq.broker.ConnectionContext;
import org.apache.activemq.broker.region.Destination;
import org.apache.activemq.broker.region.DestinationFilter;
import org.apache.activemq.broker.region.Subscription;
import org.apache.activemq.command.ActiveMQDestination;

public class MqttAuthorizationDestinationFilter extends DestinationFilter {
	private static final String TAG = " ------  MqttAuthorizationDestinationFilter  ";

	private final MqttAuthorizationBroker broker;

	public MqttAuthorizationDestinationFilter(Destination next, MqttAuthorizationBroker broker) {
		super(next);
		this.broker = broker;
	}

	@Override
	public void addSubscription(ConnectionContext context, Subscription sub) throws Exception {
		System.out.println(TAG + "addSubscription(ConnectionContext context, Subscription sub)");
		final ActiveMQDestination destination = next.getActiveMQDestination();
		String msgType = destination.getDestinationTypeAsString();
		String topic = destination.toString();

		System.out.println(TAG + "addSubscription username: " + context.getUserName() + "  topic: " + topic);

		if ("Topic".equals(msgType)) {
			if (!topic.startsWith("topic://ActiveMQ.Advisory") && !topic.startsWith("topic://$SYS")
					&& !topic.equals("topic://")) {
				if (!broker.checkDestinationAdmin(destination, context.getClientId(), context.getUserName(), topic,
						1)) {
					throw new SecurityException(
							"User " + context.getUserName() + " is not authorized to read from: " + destination);
				}
			}
		}
		super.addSubscription(context, sub);
	}
}

5. 把项目打包成 jar 文件, 放到 activemq 项目中 lib 文件夹下

0b68947994e78ebe587755ccbdac28a5.png

6. 在 conf 文件夹下的 activemq.xml 配置中配置你自定义的插件:注意class 配置中插件的全类名

e8e7edbde6d2b8f1f9d211fee1516021.png