ActiveMQ是目前較爲流行的一款開源消息服務器。最近在項目開發中,須要爲ActiveMQ開發基於IP的驗證和受權機制,所以,對ActiveMQ的安全機制進行了瞭解,如下將介紹ActiveMQ的安全機制使用及其源代碼分析。
本文開發環境介紹:
操做系統:Windows XP
Java:jdk 1.6.0_12
maven:maven 3.0.4
ActiveMQ:ActiveMQ 5.6.0sql
安全機制通常包含驗證(Authentication)和受權(Authorization)兩部分。在ActiveMQ中,驗證指經過訪問者的用戶名和密碼實現用戶身份的驗證,受權指爲消息目標(隊列或主題)的讀、寫、管理指定具備相應權限的用戶組,併爲用戶分配權限。ActiveMQ的安全機制基於插件實現。
ActiveMQ提供兩種驗證插件,分別是:
1)Simple authentication plugin-in;
2)JAAS(Java Authentication and Authorization Service)authentication plugin-in。
ActiveMQ提供一種受權插件:Authorization plugin-in。apache
1. ActiveMQ的使用安全
可從ActiveMQ官網「http://activemq.apache.org/」下載ActiveMQ的源代碼包或二進制分發包。因爲ActiveMQ使用Java開發,所以須要預先安裝jdk,另外,因爲ActiveMQ的開發使用了maven,所以,若下載的是源代碼包,須要預先安裝maven。解壓源代碼包,並在源代碼包目錄下執行「mvn install -Dmaven.test.skip=true 」完成編譯、打包和安裝,成功後,會在assembly\target下生成二進制分發包。若下載的是二進制分發包,解壓便可。
ActiveMQ的二進制分發包目錄以下所示:服務器
2. Simple authentication plugin-in的使用maven
在activemq.xml中以下配置:ide
- <plugins>
- <simpleAuthenticationPlugin>
- <users>
- <authenticationUser username="system" password="password"
- groups="users,admins"/>
- <authenticationUser username="user" password="password"
- groups="users"/>
- <authenticationUser username="guest" password="password" groups="guests"/>
- </users>
- </simpleAuthenticationPlugin>
- </plugins>
3. JAAS authentication plugin-in的使用
在activemq.xml中以下配置:ui
- <plugins>
- <jaasAuthenticationPlugin configuration="activemq-domain" />
- </plugins>
建立login.config文件:this
- activemq-domain {
- org.apache.activemq.jaas.PropertiesLoginModule required
- debug=true
- org.apache.activemq.jaas.properties.user="users.properties"
- org.apache.activemq.jaas.properties.group="groups.properties";
- };
建立users.properties和groups.properties文件,包含用戶和用戶組信息。
users.properties:spa
- system=password
- user=password
- guest=password
groups.properties:
- admins=system
- users=system,user
- guests=guest
4. Authorization plugin-in的使用
在activemq.xml中以下配置:
- <plugins>
- <authorizationPlugin>
- <map>
- <authorizationMap>
- <authorizationEntries>
- <authorizationEntry queue=">" read="admins" write="admins" admin="admins" />
- <authorizationEntry queue="USERS.>" read="users" write="users" admin="users" />
- <authorizationEntry queue="GUEST.>" read="guests" write="guests,users" admin="guests,users" />
- <authorizationEntry queue="TEST.Q" read="guests" write="guests" />
- <authorizationEntry topic=">" read="admins" write="admins" admin="admins" />
- <authorizationEntry topic="USERS.>" read="users" write="users" admin="users" />
- <authorizationEntry topic="GUEST.>" read="guests" write="guests,users" admin="guests,users" />
- <authorizationEntry topic="ActiveMQ.Advisory.>" read="guests,users" write="guests,users" admin="guests,users"/>
- </authorizationEntries>
- </authorizationMap>
- </map>
- </authorizationPlugin>
- </plugins>
ActiveMQ在其maven工程的activemq-core模塊中實現安全機制。ActiveMQ原有安全機制均基於插件實現,實現思路如圖所示。
其中,Broker接口是ActiveMQ的核心接口,ActiveMQ消息服務器對象即該接口的實現。接口BrokerPlugin經過installPlugin方法傳入Broker對象,爲其建立插件。BrokerFilter類也實現自Broker接口,其與Broker的關係,相似於Struts中Interceptor與Action的關係,多個BrokerFilter對象以及消息服務器Broker對象經過指向下一個對象的引用next構成鏈狀結構,當建立鏈接、消息生產者、消息消費者時,前後執行BrokerFilter中的相應方法,直至執行消息服務器中的方法,而安全機制類即繼承自BrokerFilter。
ActiveMQ原有安全機制的相關類均繼承或實現自上述類或接口,安全機制的類包爲activemq-core中的org.apache.activemq.security。
1. Simple authentication plugin-in的源代碼分析
Simple authentication plugin-in主要包含兩個基本類:SimpleAuthenticationPlugin(實現自BrokerPlugin)和SimpleAuthenticationBroker(繼承自BrokerFilter)。
SimpleAuthenticationPlugin部分代碼:
- public class SimpleAuthenticationPlugin implements BrokerPlugin {
- private Map<String, String> userPasswords;
- private Map<String, Set<Principal>> userGroups;
- private static final String DEFAULT_ANONYMOUS_USER = "anonymous";
- private static final String DEFAULT_ANONYMOUS_GROUP = "anonymous";
- private String anonymousUser = DEFAULT_ANONYMOUS_USER;
- private String anonymousGroup = DEFAULT_ANONYMOUS_GROUP;
- private boolean anonymousAccessAllowed = false;
- //......
- //安裝插件時,根據activemq.xml中的配置,新建 SimpleAuthenticationBroker對象, 並返回該對象
- public Broker installPlugin(Broker parent) {
- SimpleAuthenticationBroker broker = new SimpleAuthenticationBroker(parent, userPasswords, userGroups);
- broker.setAnonymousAccessAllowed(anonymousAccessAllowed);
- broker.setAnonymousUser(anonymousUser);
- broker.setAnonymousGroup(anonymousGroup);
- return broker;
- }
- //......
- }
SimpleAuthenticationBroker部分代碼:
- public class SimpleAuthenticationBroker extends BrokerFilter {
- private boolean anonymousAccessAllowed = false;
- private String anonymousUser;
- private String anonymousGroup;
- private final Map<String,String> userPasswords;
- private final Map<String,Set<Principal>> userGroups;
- private final CopyOnWriteArrayList<SecurityContext> securityContexts = new CopyOnWriteArrayList<SecurityContext>();
- //......
- //因爲驗證須要在建立鏈接時進行,所以重寫BrokerFilter的addConnection方法
- public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
- SecurityContext s = context.getSecurityContext();
- if (s == null) {
- //若容許匿名訪問,則不進行驗證
- // Check the username and password.
- if (anonymousAccessAllowed && info.getUserName() == null && info.getPassword() == null) {
- info.setUserName(anonymousUser);
- s = new SecurityContext(info.getUserName()) {
- public Set<Principal> getPrincipals() {
- Set<Principal> groups = new HashSet<Principal>();
- groups.add(new GroupPrincipal(anonymousGroup));
- return groups;
- }
- };
- //若不容許匿名訪問,則驗證鏈接的用戶名和密碼是否與配置文件中的一致,若不一致,則拋出安全異常
- } else {
- String pw = userPasswords.get(info.getUserName());
- if (pw == null || !pw.equals(info.getPassword())) {
- throw new SecurityException(
- "User name [" + info.getUserName() + "] or password is invalid.");
- }
- final Set<Principal> groups = userGroups.get(info.getUserName());
- s = new SecurityContext(info.getUserName()) {
- public Set<Principal> getPrincipals() {
- return groups;
- }
- };
- }
- context.setSecurityContext(s);
- securityContexts.add(s);
- }
- //調用父對象的addConnection方法,即調用next引用的Broker對象的addConnection方法
- try {
- super.addConnection(context, info);
- } catch (Exception e) {
- securityContexts.remove(s);
- context.setSecurityContext(null);
- throw e;
- }
- }
- //......
- }
2. JAAS authentication plugin-in的源代碼分析
JAAS authentication plugin-in主要包含兩個基本類:JaasAuthenticationPlugin(實現自BrokerPlugin)JaasAuthenticationBroker(繼承自BrokerFilter)。
JaasAuthenticationPlugin部分代碼:
- public class JaasAuthenticationPlugin implements BrokerPlugin {
- protected String configuration = "activemq-domain";
- //......
- public Broker installPlugin(Broker broker) {
- //讀取配置文件, 初始化JAAS
- initialiseJaas();
- //建立JaasAuthenticationBroker對象並返回
- return new JaasAuthenticationBroker(broker, configuration);
- }
- //......
- }
JaasAuthenticationBroker部分代碼:
- public class JaasAuthenticationBroker extends BrokerFilter {
- private final String jassConfiguration;
- private final CopyOnWriteArrayList<SecurityContext> securityContexts = new CopyOnWriteArrayList<SecurityContext>();
- //......
- //因爲驗證須要在建立鏈接時進行,所以重寫BrokerFilter的addConnection方法
- public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
- if (context.getSecurityContext() == null) {
- // Set the TCCL since it seems JAAS needs it to find the login
- // module classes.
- ClassLoader original = Thread.currentThread().getContextClassLoader();
- Thread.currentThread().setContextClassLoader(JaasAuthenticationBroker.class.getClassLoader());
- try {
- // Do the login.
- try {
- JassCredentialCallbackHandler callback = new JassCredentialCallbackHandler(info
- .getUserName(), info.getPassword());
- LoginContext lc = new LoginContext(jassConfiguration, callback);
- lc.login();
- Subject subject = lc.getSubject();
- //基於JAAS判斷用戶名和密碼是否正確
- SecurityContext s = new JaasSecurityContext(info.getUserName(), subject);
- context.setSecurityContext(s);
- securityContexts.add(s);
- } catch (Exception e) {
- throw (SecurityException)new SecurityException("User name [" + info.getUserName() + "] or password is invalid.")
- .initCause(e);
- }
- } finally {
- Thread.currentThread().setContextClassLoader(original);
- }
- }
- //調用父對象的addConnection方法,即調用next引用的Broker對象的addConnection方法
- super.addConnection(context, info);
- }
- //......
- }
3. Authorization plugin-in的源代碼分析
Authorization plugin-in主要包含兩個基本類:AuthorizationPlugin(實現自BrokerPlugin)AuthorizationBroker(繼承自BrokerFilter)。
AuthorizationPlugin部分代碼:
- public class AuthorizationPlugin implements BrokerPlugin {
- //AuthorizationMap對象存儲activemq.xml中消息目標、讀、寫、管理用戶組信息
- private AuthorizationMap map;
- //......
- //建立 AuthorizationBroker 對象並返回
- public Broker installPlugin(Broker broker) {
- if (map == null) {
- throw new IllegalArgumentException("You must configure a 'map' property");
- }
- return new AuthorizationBroker(broker, map);
- }
- //......
- }
AuthorizationBroker部分代碼:
- public class AuthorizationBroker extends BrokerFilter implements SecurityAdminMBean {
- private final AuthorizationMap authorizationMap;
- //......
- //因爲須要受權是否可管理消息目標,所以重寫BrokerFilter的 addDestinationInfo 方法
- @Override
- public void addDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
- addDestination(context, info.getDestination(),true);
- super.addDestinationInfo(context, info);
- }
- //因爲須要受權是否可管理消息目標,所以重寫BrokerFilter的 addDestination 方法
- @Override
- public Destination addDestination(ConnectionContext context, ActiveMQDestination destination,boolean create) throws Exception {
- final SecurityContext securityContext = context.getSecurityContext();
- if (securityContext == null) {
- throw new SecurityException("User is not authenticated.");
- }
- Destination existing = this.getDestinationMap().get(destination);
- if (existing != null) {
- return super.addDestination(context, destination,create);
- }
- //從訪問控制列表中查看是否具備受權
- if (!securityContext.isBrokerContext()) {
- Set<?> allowedACLs = null;
- if (!destination.isTemporary()) {
- allowedACLs = authorizationMap.getAdminACLs(destination);
- } else {
- allowedACLs = authorizationMap.getTempDestinationAdminACLs();
- }
- if (allowedACLs != null && !securityContext.isInOneOf(allowedACLs)) {
- throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to create: " + destination);
- }
- }
- //調用next引用的addDestination方法
- return super.addDestination(context, destination,create);
- }
- //因爲須要受權是否可讀消息,所以重寫BrokerFilter的 addConsumer 方法,在該方法中,從訪問控制列表中查看是否具備讀受權,並調用next引用的addConsumer方法
- @Override
- public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
- //......
- return super.addConsumer(context, info);
- }
- //因爲須要受權是否可寫消息,所以重寫BrokerFilter的 addProducer 方法,在該方法中,從訪問控制列表中查看是否具備寫受權,並調用next引用的 addProducer 方法
- @Override
- public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception {
- //......
- super.addProducer(context, info);
- }
- //......
- }
ActiveMQ提供了便利的插件開發方式,並基於插件實現了包含驗證和受權的安全機制。參考ActiveMQ的源代碼,能夠進行插件開發,實現個性化的安全機制,如基於IP的驗證和受權。