講解完http標簽的解析過程,authentication-manager標簽解析部分就很容易理解了
authentication-manager標簽在spring的配置文件中的定義一般如下
1 <authentication-manager alias="authenticationManager"> 2 <authentication-provider user-service-ref="userDetailsManager"/> 3 </authentication-manager>
authentication-manager標簽的解析類是:
org.springframework.security.config.authentication.AuthenticationManagerBeanDefinitionParser
具體解析方法parse的代碼為
1 public BeanDefinition parse(Element element, ParserContext pc) { 2 Assert.state(!pc.getRegistry().containsBeanDefinition(BeanIds.AUTHENTICATION_MANAGER), 3 "AuthenticationManager has already been registered!"); 4 pc.pushContainingComponent(new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element))); 5 //構造ProviderManager的BeanDefinition 6 BeanDefinitionBuilder providerManagerBldr = BeanDefinitionBuilder.rootBeanDefinition(ProviderManager.class); 7 //獲取alias屬性 8 String alias = element.getAttribute(ATT_ALIAS); 9 //檢查session-controller-ref屬性,提示通過標簽<concurrent-session-control>取代 10 checkForDeprecatedSessionControllerRef(element, pc); 11 List<BeanMetadataElement> providers = new ManagedList<BeanMetadataElement>(); 12 NamespaceHandlerResolver resolver = pc.getReaderContext().getNamespaceHandlerResolver(); 13 //獲取authentication-manager的子節點 14 NodeList children = element.getChildNodes(); 15 //循環節點,一般子節點主要是authentication-provider或者 16 //ldap-authentication-provider 17 for (int i = 0; i < children.getLength(); i++) { 18 Node node = children.item(i); 19 if (node instanceof Element) { 20 Element providerElt = (Element)node; 21 //判斷子標簽是否有ref屬性,如果有,則直接將ref屬性 22 //引用的bean id添加到providers集合中 23 if (StringUtils.hasText(providerElt.getAttribute(ATT_REF))) { 24 providers.add(new RuntimeBeanReference(providerElt.getAttribute(ATT_REF))); 25 } else { 26 //如果沒有ref屬性,則通過子標簽的解析類完成標簽解析 27 //如子標簽:authentication-provider,解析過程在后面 28 BeanDefinition provider = resolver.resolve(providerElt.getNamespaceURI()).parse(providerElt, pc); 29 Assert.notNull(provider, "Parser for " + providerElt.getNodeName() + " returned a null bean definition"); 30 String id = pc.getReaderContext().generateBeanName(provider); 31 //注冊provider的BeanDefinition 32 pc.registerBeanComponent(new BeanComponentDefinition(provider, id)); 33 //添加注冊過的bean到provider集合中 34 providers.add(new RuntimeBeanReference(id)); 35 } 36 } 37 } 38 39 if (providers.isEmpty()) { 40 providers.add(new RootBeanDefinition(NullAuthenticationProvider.class)); 41 } 42 43 providerManagerBldr.addPropertyValue("providers", providers); 44 //添加默認的事件發布類 45 BeanDefinition publisher = new RootBeanDefinition(DefaultAuthenticationEventPublisher.class); 46 String id = pc.getReaderContext().generateBeanName(publisher); 47 pc.registerBeanComponent(new BeanComponentDefinition(publisher, id)); 48 //將事件發布類的bean注入到ProviderManager的 49 //authenticationEventPublisher屬性中 50 providerManagerBldr.addPropertyReference("authenticationEventPublisher", id); 51 //注冊ProviderManager的bean 52 pc.registerBeanComponent( 53 new BeanComponentDefinition(providerManagerBldr.getBeanDefinition(), BeanIds.AUTHENTICATION_MANAGER)); 54 55 if (StringUtils.hasText(alias)) { 56 pc.getRegistry().registerAlias(BeanIds.AUTHENTICATION_MANAGER, alias); 57 pc.getReaderContext().fireAliasRegistered(BeanIds.AUTHENTICATION_MANAGER, alias, pc.extractSource(element)); 58 } 59 60 pc.popAndRegisterContainingComponent(); 61 62 return null; 63 }
通過上面的代碼片段,能夠知道authentication-manager標簽解析的步驟是
1.構造ProviderManager的BeanDefinition
2.循環authentication-manager的子標簽,構造provider的BeanDefinition,並添加到providers集合中
3.將第2步的providers設置為ProviderManager的providers屬性
4.構造異常事件發布類DefaultAuthenticationEventPublisher的BeanDefinition,並設置為ProviderManager的屬性authenticationEventPublisher
5.通過registerBeanComponent方法完成bean的注冊任務
authentication-provider標簽的解析類為
org.springframework.security.config.authentication.AuthenticationProviderBeanDefinitionParser
1 public BeanDefinition parse(Element element, ParserContext parserContext) { 2 //首先構造DaoAuthenticationProvider的BeanDefinition 3 RootBeanDefinition authProvider = new RootBeanDefinition(DaoAuthenticationProvider.class); 4 authProvider.setSource(parserContext.extractSource(element)); 5 //獲取password-encoder子標簽 6 Element passwordEncoderElt = DomUtils.getChildElementByTagName(element, Elements.PASSWORD_ENCODER); 7 8 if (passwordEncoderElt != null) { 9 //如果有password-encoder子標簽,把解析任務交給 10 //PasswordEncoderParser完成 11 PasswordEncoderParser pep = new PasswordEncoderParser(passwordEncoderElt, parserContext); 12 authProvider.getPropertyValues().addPropertyValue("passwordEncoder", pep.getPasswordEncoder()); 13 //如果有salt-source標簽,將值注入到saltSource屬性中 14 if (pep.getSaltSource() != null) { 15 authProvider.getPropertyValues().addPropertyValue("saltSource", pep.getSaltSource()); 16 } 17 } 18 //下面獲取子標簽user-service、jdbc-user-service、ldap-user-service 19 Element userServiceElt = DomUtils.getChildElementByTagName(element, Elements.USER_SERVICE); 20 Element jdbcUserServiceElt = DomUtils.getChildElementByTagName(element, Elements.JDBC_USER_SERVICE); 21 Element ldapUserServiceElt = DomUtils.getChildElementByTagName(element, Elements.LDAP_USER_SERVICE); 22 23 String ref = element.getAttribute(ATT_USER_DETAILS_REF); 24 25 if (StringUtils.hasText(ref)) { 26 if (userServiceElt != null || jdbcUserServiceElt != null || ldapUserServiceElt != null) { 27 parserContext.getReaderContext().error("The " + ATT_USER_DETAILS_REF + " attribute cannot be used in combination with child" + 28 "elements '" + Elements.USER_SERVICE + "', '" + Elements.JDBC_USER_SERVICE + "' or '" + 29 Elements.LDAP_USER_SERVICE + "'", element); 30 } 31 } else { 32 // Use the child elements to create the UserDetailsService 33 AbstractUserDetailsServiceBeanDefinitionParser parser = null; 34 Element elt = null; 35 //下面的if語句,主要是根據子標簽的不同,選擇子標簽對應的解析器處理 36 if (userServiceElt != null) { 37 elt = userServiceElt; 38 parser = new UserServiceBeanDefinitionParser(); 39 } else if (jdbcUserServiceElt != null) { 40 elt = jdbcUserServiceElt; 41 parser = new JdbcUserServiceBeanDefinitionParser(); 42 } else if (ldapUserServiceElt != null) { 43 elt = ldapUserServiceElt; 44 parser = new LdapUserServiceBeanDefinitionParser(); 45 } else { 46 parserContext.getReaderContext().error("A user-service is required", element); 47 } 48 49 parser.parse(elt, parserContext); 50 ref = parser.getId(); 51 String cacheRef = elt.getAttribute(AbstractUserDetailsServiceBeanDefinitionParser.CACHE_REF); 52 53 if (StringUtils.hasText(cacheRef)) { 54 authProvider.getPropertyValues().addPropertyValue("userCache", new RuntimeBeanReference(cacheRef)); 55 } 56 } 57 //將解析后的bean id注入到userDetailsService屬性中 58 authProvider.getPropertyValues().addPropertyValue("userDetailsService", new RuntimeBeanReference(ref)); 59 return authProvider; 60 }
如果學習過acegi的配置,應該知道,acegi有這么一段配置
1 <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager"> 2 <property name="providers"> 3 <list> 4 <ref local="daoAuthenticationProvider"/> 5 <ref local="anonymousAuthenticationProvider"/> 6 </list> 7 </property> 8 </bean>
實際上authentication-manager標簽所要達到的目標就是構造上面的bean。其中anonymousAuthenticationProvider是在http解析過程添加的。
其實可以完全像acegi那樣自定義每個bean。
1 <authentication-manager alias="authenticationManager"> 2 <authentication-provider user-service-ref="userDetailsManager"/> 3 </authentication-manager>
上面的標簽如果用bean來定義,則可以完全由下面的xml來替代。
1 <bean id="org.springframework.security.authenticationManager" class="org.springframework.security.authentication.ProviderManager"> 2 <property name="authenticationEventPublisher" ref="defaultAuthenticationEventPublisher"></property> 3 <property name="providers"> 4 <list> 5 <ref local="daoAuthenticationProvider"/> 6 <ref local="anonymousAuthenticationProvider"/> 7 </list> 8 </property> 9 </bean> 10 11 <bean id="defaultAuthenticationEventPublisher" class="org.springframework.security.authentication.DefaultAuthenticationEventPublisher"></bean> 12 13 <bean id="anonymousAuthenticationProvider" class="org.springframework.security.authentication.AnonymousAuthenticationProvider"> 14 <property name="key"><value>work</value></property> 15 </bean> 16 17 <bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider"> 18 <property name="userDetailsService" ref="userDetailsManager"></property> 19 </bean>
需要注意的是anonymousAuthenticationProvider的bean中,需要增加key屬性。如果采用authentication-manager標簽的方式,key雖然沒有定義,在增加AnonymousAuthenticationFilter過濾器中,是通過java.security.SecureRandom.nextLong()來生成的。
顯而易見,如果采用bean的方式來定義,非常復雜,而且需要了解底層的組裝過程才行,不過能夠提高更大的擴展性。采用authentication-manager標簽的方式,很簡潔,只需要提供UserDetailsService即可。