讲解完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即可。