ActiveMQ的安全機制使用及其源代碼分析

ActiveMQ是目前較爲流行的一款開源消息服務器。最近在項目開發中,須要爲ActiveMQ開發基於IP的驗證和受權機制,所以,對ActiveMQ的安全機制進行了瞭解,如下將介紹ActiveMQ的安全機制使用及其源代碼分析。
本文開發環境介紹:
操做系統:Windows XP
Java:jdk 1.6.0_12
maven:maven 3.0.4
ActiveMQ:ActiveMQ 5.6.0sql

  • ActiveMQ安全機制的介紹

安全機制通常包含驗證(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

  • ActiveMQ安全機制的使用

     1. ActiveMQ的使用安全

可從ActiveMQ官網「http://activemq.apache.org/」下載ActiveMQ的源代碼包或二進制分發包。因爲ActiveMQ使用Java開發,所以須要預先安裝jdk,另外,因爲ActiveMQ的開發使用了maven,所以,若下載的是源代碼包,須要預先安裝maven。解壓源代碼包,並在源代碼包目錄下執行「mvn install -Dmaven.test.skip=true 」完成編譯、打包和安裝,成功後,會在assembly\target下生成二進制分發包。若下載的是二進制分發包,解壓便可。
ActiveMQ的二進制分發包目錄以下所示:服務器


進入bin文件,執行腳本,便可運行ActiveMQ。dom

     2. Simple authentication plugin-in的使用maven

在activemq.xml中以下配置:ide

  
  
  
  
  1. <plugins> 
  2.     <simpleAuthenticationPlugin> 
  3.         <users> 
  4.             <authenticationUser username="system" password="password" 
  5.                 groups="users,admins"/> 
  6.             <authenticationUser username="user" password="password" 
  7.                 groups="users"/> 
  8.             <authenticationUser username="guest" password="password" groups="guests"/> 
  9.         </users> 
  10.     </simpleAuthenticationPlugin> 
  11. </plugins>   

     3. JAAS authentication plugin-in的使用
在activemq.xml中以下配置:ui

  
  
  
  
  1. <plugins> 
  2.     <jaasAuthenticationPlugin configuration="activemq-domain" /> 
  3. </plugins>   

建立login.config文件:this

  
  
  
  
  1. activemq-domain { 
  2.   org.apache.activemq.jaas.PropertiesLoginModule required 
  3.     debug=true 
  4.     org.apache.activemq.jaas.properties.user="users.properties" 
  5.     org.apache.activemq.jaas.properties.group="groups.properties"
  6. }; 

建立users.properties和groups.properties文件,包含用戶和用戶組信息。
users.properties:spa

  
  
  
  
  1. system=password 
  2. user=password 
  3. guest=password 

groups.properties:

  
  
  
  
  1. admins=system 
  2. users=system,user 
  3. guests=guest 

     4. Authorization plugin-in的使用
在activemq.xml中以下配置:

  
  
  
  
  1. <plugins> 
  2.   <authorizationPlugin> 
  3.     <map> 
  4.       <authorizationMap> 
  5.         <authorizationEntries> 
  6.           <authorizationEntry queue=">" read="admins" write="admins" admin="admins" /> 
  7.           <authorizationEntry queue="USERS.>" read="users" write="users" admin="users" /> 
  8.           <authorizationEntry queue="GUEST.>" read="guests" write="guests,users" admin="guests,users" /> 
  9.           
  10.           <authorizationEntry queue="TEST.Q" read="guests" write="guests" /> 
  11.           
  12.           <authorizationEntry topic=">" read="admins" write="admins" admin="admins" /> 
  13.           <authorizationEntry topic="USERS.>" read="users" write="users" admin="users" /> 
  14.           <authorizationEntry topic="GUEST.>" read="guests" write="guests,users" admin="guests,users" /> 
  15.           
  16.           <authorizationEntry topic="ActiveMQ.Advisory.>" read="guests,users" write="guests,users" admin="guests,users"/> 
  17.         </authorizationEntries> 
  18.       </authorizationMap> 
  19.     </map> 
  20.   </authorizationPlugin> 
  21. </plugins>  
  • ActiveMQ安全機制的源代碼分析

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部分代碼:

  
  
  
  
  1. public class SimpleAuthenticationPlugin implements BrokerPlugin { 
  2.     private Map<String, String> userPasswords; 
  3.     private Map<String, Set<Principal>> userGroups; 
  4.     private static final String DEFAULT_ANONYMOUS_USER = "anonymous"
  5.     private static final String DEFAULT_ANONYMOUS_GROUP = "anonymous"
  6.     private String anonymousUser = DEFAULT_ANONYMOUS_USER; 
  7.     private String anonymousGroup = DEFAULT_ANONYMOUS_GROUP; 
  8.     private boolean anonymousAccessAllowed = false
  9.     //...... 
  10.     //安裝插件時,根據activemq.xml中的配置,新建 SimpleAuthenticationBroker對象, 並返回該對象  
  11.     public Broker installPlugin(Broker parent) { 
  12.         SimpleAuthenticationBroker broker = new SimpleAuthenticationBroker(parent, userPasswords, userGroups); 
  13.         broker.setAnonymousAccessAllowed(anonymousAccessAllowed); 
  14.         broker.setAnonymousUser(anonymousUser); 
  15.         broker.setAnonymousGroup(anonymousGroup); 
  16.         return broker; 
  17.     } 
  18.     //...... 

SimpleAuthenticationBroker部分代碼:

  
  
  
  
  1. public class SimpleAuthenticationBroker extends BrokerFilter { 
  2.  
  3.     private boolean anonymousAccessAllowed = false
  4.     private String anonymousUser; 
  5.     private String anonymousGroup; 
  6.     private final Map<String,String> userPasswords; 
  7.     private final Map<String,Set<Principal>> userGroups; 
  8.     private final CopyOnWriteArrayList<SecurityContext> securityContexts = new CopyOnWriteArrayList<SecurityContext>(); 
  9.  
  10.     //...... 
  11.     //因爲驗證須要在建立鏈接時進行,所以重寫BrokerFilter的addConnection方法 
  12.     public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception { 
  13.  
  14.         SecurityContext s = context.getSecurityContext(); 
  15.         if (s == null) { 
  16.             
  17.      //若容許匿名訪問,則不進行驗證 
  18.      // Check the username and password. 
  19.             if (anonymousAccessAllowed && info.getUserName() == null && info.getPassword() == null) { 
  20.                 info.setUserName(anonymousUser); 
  21.                 s = new SecurityContext(info.getUserName()) { 
  22.                     public Set<Principal> getPrincipals() { 
  23.                         Set<Principal> groups = new HashSet<Principal>(); 
  24.                         groups.add(new GroupPrincipal(anonymousGroup)); 
  25.                         return groups; 
  26.                     } 
  27.                 }; 
  28.      //若不容許匿名訪問,則驗證鏈接的用戶名和密碼是否與配置文件中的一致,若不一致,則拋出安全異常           
  29.   } else { 
  30.                 String pw = userPasswords.get(info.getUserName()); 
  31.                 if (pw == null || !pw.equals(info.getPassword())) { 
  32.                     throw new SecurityException( 
  33.                             "User name [" + info.getUserName() + "] or password is invalid."); 
  34.                 } 
  35.  
  36.                 final Set<Principal> groups = userGroups.get(info.getUserName()); 
  37.                 s = new SecurityContext(info.getUserName()) { 
  38.                     public Set<Principal> getPrincipals() { 
  39.                         return groups; 
  40.                     } 
  41.                 }; 
  42.             } 
  43.  
  44.             context.setSecurityContext(s); 
  45.             securityContexts.add(s); 
  46.         } 
  47.         
  48.     //調用父對象的addConnection方法,即調用next引用的Broker對象的addConnection方法 
  49. try { 
  50.             super.addConnection(context, info); 
  51.         } catch (Exception e) { 
  52.             securityContexts.remove(s); 
  53.             context.setSecurityContext(null); 
  54.             throw e; 
  55.         } 
  56.     } 
  57.     //...... 

     2. JAAS authentication plugin-in的源代碼分析
JAAS authentication plugin-in主要包含兩個基本類:JaasAuthenticationPlugin(實現自BrokerPlugin)JaasAuthenticationBroker(繼承自BrokerFilter)。
JaasAuthenticationPlugin部分代碼:

  
  
  
  
  1. public class JaasAuthenticationPlugin implements BrokerPlugin { 
  2.     protected String configuration = "activemq-domain"
  3.     //...... 
  4. public Broker installPlugin(Broker broker) { 
  5. //讀取配置文件, 初始化JAAS    
  6. initialiseJaas(); 
  7. //建立JaasAuthenticationBroker對象並返回        
  8. return new JaasAuthenticationBroker(broker, configuration); 
  9.   } 
  10.    //...... 

JaasAuthenticationBroker部分代碼:

  
  
  
  
  1. public class JaasAuthenticationBroker extends BrokerFilter { 
  2.  
  3.     private final String jassConfiguration; 
  4.     private final CopyOnWriteArrayList<SecurityContext> securityContexts = new CopyOnWriteArrayList<SecurityContext>(); 
  5.  
  6. //...... 
  7. //因爲驗證須要在建立鏈接時進行,所以重寫BrokerFilter的addConnection方法    
  8. public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception { 
  9.  
  10.         if (context.getSecurityContext() == null) { 
  11.             // Set the TCCL since it seems JAAS needs it to find the login 
  12.             // module classes. 
  13.             ClassLoader original = Thread.currentThread().getContextClassLoader(); 
  14.             Thread.currentThread().setContextClassLoader(JaasAuthenticationBroker.class.getClassLoader()); 
  15.             try { 
  16.                 // Do the login. 
  17.                 try { 
  18.                     JassCredentialCallbackHandler callback = new JassCredentialCallbackHandler(info 
  19.                         .getUserName(), info.getPassword()); 
  20.                     LoginContext lc = new LoginContext(jassConfiguration, callback); 
  21.                     lc.login(); 
  22.                     Subject subject = lc.getSubject(); 
  23.  
  24. //基於JAAS判斷用戶名和密碼是否正確 
  25.                     SecurityContext s = new JaasSecurityContext(info.getUserName(), subject); 
  26.                     context.setSecurityContext(s); 
  27.                     securityContexts.add(s); 
  28.                 } catch (Exception e) { 
  29.                     throw (SecurityException)new SecurityException("User name [" + info.getUserName() + "] or password is invalid."
  30.                         .initCause(e); 
  31.                 } 
  32.             } finally { 
  33.                 Thread.currentThread().setContextClassLoader(original); 
  34.             } 
  35.         } 
  36. //調用父對象的addConnection方法,即調用next引用的Broker對象的addConnection方法 
  37.         super.addConnection(context, info); 
  38.     } 
  39. //...... 

     3. Authorization plugin-in的源代碼分析
Authorization plugin-in主要包含兩個基本類:AuthorizationPlugin(實現自BrokerPlugin)AuthorizationBroker(繼承自BrokerFilter)。
AuthorizationPlugin部分代碼:

  
  
  
  
  1. public class AuthorizationPlugin implements BrokerPlugin { 
  2.  
  3. //AuthorizationMap對象存儲activemq.xml中消息目標、讀、寫、管理用戶組信息 
  4.     private AuthorizationMap map; 
  5.  
  6. //...... 
  7. //建立 AuthorizationBroker 對象並返回     
  8. public Broker installPlugin(Broker broker) { 
  9.         if (map == null) { 
  10.             throw new IllegalArgumentException("You must configure a 'map' property"); 
  11.         } 
  12.         return new AuthorizationBroker(broker, map); 
  13.     } 
  14. //...... 

AuthorizationBroker部分代碼:

  
  
  
  
  1. public class AuthorizationBroker extends BrokerFilter implements SecurityAdminMBean { 
  2.  
  3.     private final AuthorizationMap authorizationMap; 
  4.  
  5. //......     
  6. //因爲須要受權是否可管理消息目標,所以重寫BrokerFilter的 addDestinationInfo 方法   
  7.     @Override 
  8.     public void addDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception { 
  9.         addDestination(context, info.getDestination(),true); 
  10.         super.addDestinationInfo(context, info); 
  11.     } 
  12.  
  13. //因爲須要受權是否可管理消息目標,所以重寫BrokerFilter的  addDestination  方法      
  14. @Override 
  15.     public Destination addDestination(ConnectionContext context, ActiveMQDestination destination,boolean create) throws Exception { 
  16.         final SecurityContext securityContext = context.getSecurityContext(); 
  17.         if (securityContext == null) { 
  18.             throw new SecurityException("User is not authenticated."); 
  19.         } 
  20.         
  21.         Destination existing = this.getDestinationMap().get(destination); 
  22.         if (existing != null) { 
  23.             return super.addDestination(context, destination,create); 
  24.         } 
  25.         
  26. //從訪問控制列表中查看是否具備受權        
  27. if (!securityContext.isBrokerContext()) { 
  28.             Set<?> allowedACLs = null
  29.             if (!destination.isTemporary()) { 
  30.                 allowedACLs = authorizationMap.getAdminACLs(destination); 
  31.             } else { 
  32.                 allowedACLs = authorizationMap.getTempDestinationAdminACLs(); 
  33.             } 
  34.  
  35.             if (allowedACLs != null && !securityContext.isInOneOf(allowedACLs)) { 
  36.                 throw new SecurityException("User " + securityContext.getUserName() + " is not authorized to create: " + destination); 
  37.             } 
  38.  
  39.         } 
  40.  
  41. //調用next引用的addDestination方法        
  42. return super.addDestination(context, destination,create); 
  43.     } 
  44.  
  45.     
  46. //因爲須要受權是否可讀消息,所以重寫BrokerFilter的  addConsumer  方法,在該方法中,從訪問控制列表中查看是否具備讀受權,並調用next引用的addConsumer方法         
  47. @Override 
  48.     public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {  
  49.       //......  
  50.         return super.addConsumer(context, info); 
  51.     } 
  52.  
  53. //因爲須要受權是否可寫消息,所以重寫BrokerFilter的  addProducer   方法,在該方法中,從訪問控制列表中查看是否具備寫受權,並調用next引用的 addProducer 方法      
  54. @Override 
  55.     public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception {  
  56.        //......  
  57.         super.addProducer(context, info); 
  58.     } 
  59. //......  
  • 總結

ActiveMQ提供了便利的插件開發方式,並基於插件實現了包含驗證和受權的安全機制。參考ActiveMQ的源代碼,能夠進行插件開發,實現個性化的安全機制,如基於IP的驗證和受權。  

相關文章
相關標籤/搜索