/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.security.config.http; import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.ATT_REQUEST_MATCHER_REF; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.w3c.dom.Element; import org.springframework.beans.BeanMetadataElement; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.ManagedMap; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.access.SecurityConfig; import org.springframework.security.config.Elements; import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler; import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource; import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; /** * Allows for convenient creation of a {@link FilterInvocationSecurityMetadataSource} bean * for use with a FilterSecurityInterceptor. * * @author Luke Taylor */ public class FilterInvocationSecurityMetadataSourceParser implements BeanDefinitionParser { private static final String ATT_USE_EXPRESSIONS = "use-expressions"; private static final String ATT_HTTP_METHOD = "method"; private static final String ATT_PATTERN = "pattern"; private static final String ATT_ACCESS = "access"; private static final String ATT_SERVLET_PATH = "servlet-path"; private static final Log logger = LogFactory .getLog(FilterInvocationSecurityMetadataSourceParser.class); public BeanDefinition parse(Element element, ParserContext parserContext) { List<Element> interceptUrls = DomUtils.getChildElementsByTagName(element, Elements.INTERCEPT_URL); // Check for attributes that aren't allowed in this context for (Element elt : interceptUrls) { if (StringUtils.hasLength(elt .getAttribute(HttpSecurityBeanDefinitionParser.ATT_REQUIRES_CHANNEL))) { parserContext.getReaderContext().error( "The attribute '" + HttpSecurityBeanDefinitionParser.ATT_REQUIRES_CHANNEL + "' isn't allowed here.", elt); } if (StringUtils.hasLength(elt .getAttribute(HttpSecurityBeanDefinitionParser.ATT_FILTERS))) { parserContext.getReaderContext().error( "The attribute '" + HttpSecurityBeanDefinitionParser.ATT_FILTERS + "' isn't allowed here.", elt); } if (StringUtils.hasLength(elt.getAttribute(ATT_SERVLET_PATH))) { parserContext.getReaderContext().error( "The attribute '" + ATT_SERVLET_PATH + "' isn't allowed here.", elt); } } BeanDefinition mds = createSecurityMetadataSource(interceptUrls, false, element, parserContext); String id = element.getAttribute(AbstractBeanDefinitionParser.ID_ATTRIBUTE); if (StringUtils.hasText(id)) { parserContext.registerComponent(new BeanComponentDefinition(mds, id)); parserContext.getRegistry().registerBeanDefinition(id, mds); } return mds; } static RootBeanDefinition createSecurityMetadataSource(List<Element> interceptUrls, boolean addAllAuth, Element httpElt, ParserContext pc) { MatcherType matcherType = MatcherType.fromElement(httpElt); boolean useExpressions = isUseExpressions(httpElt); ManagedMap<BeanMetadataElement, BeanDefinition> requestToAttributesMap = parseInterceptUrlsForFilterInvocationRequestMap( matcherType, interceptUrls, useExpressions, addAllAuth, pc); BeanDefinitionBuilder fidsBuilder; if (useExpressions) { Element expressionHandlerElt = DomUtils.getChildElementByTagName(httpElt, Elements.EXPRESSION_HANDLER); String expressionHandlerRef = expressionHandlerElt == null ? null : expressionHandlerElt.getAttribute("ref"); if (StringUtils.hasText(expressionHandlerRef)) { logger.info("Using bean '" + expressionHandlerRef + "' as web SecurityExpressionHandler implementation"); } else { expressionHandlerRef = registerDefaultExpressionHandler(pc); } fidsBuilder = BeanDefinitionBuilder .rootBeanDefinition(ExpressionBasedFilterInvocationSecurityMetadataSource.class); fidsBuilder.addConstructorArgValue(requestToAttributesMap); fidsBuilder.addConstructorArgReference(expressionHandlerRef); } else { fidsBuilder = BeanDefinitionBuilder .rootBeanDefinition(DefaultFilterInvocationSecurityMetadataSource.class); fidsBuilder.addConstructorArgValue(requestToAttributesMap); } fidsBuilder.getRawBeanDefinition().setSource(pc.extractSource(httpElt)); return (RootBeanDefinition) fidsBuilder.getBeanDefinition(); } static String registerDefaultExpressionHandler(ParserContext pc) { BeanDefinition expressionHandler = GrantedAuthorityDefaultsParserUtils.registerWithDefaultRolePrefix(pc, DefaultWebSecurityExpressionHandlerBeanFactory.class); String expressionHandlerRef = pc.getReaderContext().generateBeanName( expressionHandler); pc.registerBeanComponent(new BeanComponentDefinition(expressionHandler, expressionHandlerRef)); return expressionHandlerRef; } static boolean isUseExpressions(Element elt) { String useExpressions = elt.getAttribute(ATT_USE_EXPRESSIONS); return !StringUtils.hasText(useExpressions) || "true".equals(useExpressions); } private static ManagedMap<BeanMetadataElement, BeanDefinition> parseInterceptUrlsForFilterInvocationRequestMap( MatcherType matcherType, List<Element> urlElts, boolean useExpressions, boolean addAuthenticatedAll, ParserContext parserContext) { ManagedMap<BeanMetadataElement, BeanDefinition> filterInvocationDefinitionMap = new ManagedMap<BeanMetadataElement, BeanDefinition>(); for (Element urlElt : urlElts) { String access = urlElt.getAttribute(ATT_ACCESS); if (!StringUtils.hasText(access)) { continue; } String path = urlElt.getAttribute(ATT_PATTERN); String matcherRef = urlElt.getAttribute(ATT_REQUEST_MATCHER_REF); boolean hasMatcherRef = StringUtils.hasText(matcherRef); if (!hasMatcherRef && !StringUtils.hasText(path)) { parserContext.getReaderContext().error( "path attribute cannot be empty or null", urlElt); } String method = urlElt.getAttribute(ATT_HTTP_METHOD); if (!StringUtils.hasText(method)) { method = null; } String servletPath = urlElt.getAttribute(ATT_SERVLET_PATH); if (!StringUtils.hasText(servletPath)) { servletPath = null; } else if (!MatcherType.mvc.equals(matcherType)) { parserContext.getReaderContext().error( ATT_SERVLET_PATH + " is not applicable for request-matcher: '" + matcherType.name() + "'", urlElt); } BeanMetadataElement matcher = hasMatcherRef ? new RuntimeBeanReference(matcherRef) : matcherType.createMatcher(parserContext, path, method, servletPath); BeanDefinitionBuilder attributeBuilder = BeanDefinitionBuilder .rootBeanDefinition(SecurityConfig.class); if (useExpressions) { logger.info("Creating access control expression attribute '" + access + "' for " + path); // The single expression will be parsed later by the // ExpressionBasedFilterInvocationSecurityMetadataSource attributeBuilder.addConstructorArgValue(new String[] { access }); attributeBuilder.setFactoryMethod("createList"); } else { attributeBuilder.addConstructorArgValue(access); attributeBuilder.setFactoryMethod("createListFromCommaDelimitedString"); } if (filterInvocationDefinitionMap.containsKey(matcher)) { logger.warn("Duplicate URL defined: " + path + ". The original attribute values will be overwritten"); } filterInvocationDefinitionMap.put(matcher, attributeBuilder.getBeanDefinition()); } if (addAuthenticatedAll && filterInvocationDefinitionMap.isEmpty()) { BeanDefinition matcher = matcherType.createMatcher(parserContext, "/**", null); BeanDefinitionBuilder attributeBuilder = BeanDefinitionBuilder .rootBeanDefinition(SecurityConfig.class); attributeBuilder.addConstructorArgValue(new String[] { "authenticated" }); attributeBuilder.setFactoryMethod("createList"); filterInvocationDefinitionMap.put(matcher, attributeBuilder.getBeanDefinition()); } return filterInvocationDefinitionMap; } static class DefaultWebSecurityExpressionHandlerBeanFactory extends GrantedAuthorityDefaultsParserUtils.AbstractGrantedAuthorityDefaultsBeanFactory { private DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler(); public DefaultWebSecurityExpressionHandler getBean() { handler.setDefaultRolePrefix(this.rolePrefix); return handler; } } }