/* * 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.annotation.method.configuration; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.aopalliance.intercept.MethodInterceptor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.type.AnnotationMetadata; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.AfterInvocationProvider; import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.annotation.Jsr250MethodSecurityMetadataSource; import org.springframework.security.access.annotation.Jsr250Voter; import org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory; import org.springframework.security.access.expression.method.ExpressionBasedPostInvocationAdvice; import org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.intercept.AfterInvocationManager; import org.springframework.security.access.intercept.AfterInvocationProviderManager; import org.springframework.security.access.intercept.RunAsManager; import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor; import org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor; import org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource; import org.springframework.security.access.method.MethodSecurityMetadataSource; import org.springframework.security.access.prepost.PostInvocationAdviceProvider; import org.springframework.security.access.prepost.PreInvocationAuthorizationAdvice; import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter; import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource; import org.springframework.security.access.vote.AffirmativeBased; import org.springframework.security.access.vote.AuthenticatedVoter; import org.springframework.security.access.vote.RoleVoter; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.core.GrantedAuthorityDefaults; import org.springframework.util.Assert; /** * Base {@link Configuration} for enabling global method security. Classes may extend this * class to customize the defaults, but must be sure to specify the * {@link EnableGlobalMethodSecurity} annotation on the subclass. * * @author Rob Winch * @author EddĂș MelĂ©ndez * @since 3.2 * @see EnableGlobalMethodSecurity */ @Configuration public class GlobalMethodSecurityConfiguration implements ImportAware, SmartInitializingSingleton { private static final Log logger = LogFactory .getLog(GlobalMethodSecurityConfiguration.class); private ObjectPostProcessor<Object> objectPostProcessor = new ObjectPostProcessor<Object>() { public <T> T postProcess(T object) { throw new IllegalStateException(ObjectPostProcessor.class.getName() + " is a required bean. Ensure you have used @" + EnableGlobalMethodSecurity.class.getName()); } }; private DefaultMethodSecurityExpressionHandler defaultMethodExpressionHandler = new DefaultMethodSecurityExpressionHandler(); private AuthenticationManager authenticationManager; private AuthenticationManagerBuilder auth; private boolean disableAuthenticationRegistry; private AnnotationAttributes enableMethodSecurity; private ApplicationContext context; private MethodSecurityExpressionHandler expressionHandler; private Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource; private MethodSecurityInterceptor methodSecurityInterceptor; /** * Creates the default MethodInterceptor which is a MethodSecurityInterceptor using * the following methods to construct it. * <ul> * <li>{@link #accessDecisionManager()}</li> * <li>{@link #afterInvocationManager()}</li> * <li>{@link #authenticationManager()}</li> * <li>{@link #methodSecurityMetadataSource()}</li> * <li>{@link #runAsManager()}</li> * * </ul> * * <p> * Subclasses can override this method to provide a different * {@link MethodInterceptor}. * </p> * * @return * @throws Exception */ @Bean public MethodInterceptor methodSecurityInterceptor() throws Exception { this.methodSecurityInterceptor = isAspectJ() ? new AspectJMethodSecurityInterceptor() : new MethodSecurityInterceptor(); methodSecurityInterceptor.setAccessDecisionManager(accessDecisionManager()); methodSecurityInterceptor.setAfterInvocationManager(afterInvocationManager()); methodSecurityInterceptor .setSecurityMetadataSource(methodSecurityMetadataSource()); RunAsManager runAsManager = runAsManager(); if (runAsManager != null) { methodSecurityInterceptor.setRunAsManager(runAsManager); } return this.methodSecurityInterceptor; } /* * (non-Javadoc) * * @see org.springframework.beans.factory.SmartInitializingSingleton# * afterSingletonsInstantiated() */ @Override public void afterSingletonsInstantiated() { try { initializeMethodSecurityInterceptor(); } catch (Exception e) { throw new RuntimeException(e); } PermissionEvaluator permissionEvaluator = getSingleBeanOrNull( PermissionEvaluator.class); if (permissionEvaluator != null) { this.defaultMethodExpressionHandler .setPermissionEvaluator(permissionEvaluator); } RoleHierarchy roleHierarchy = getSingleBeanOrNull(RoleHierarchy.class); if (roleHierarchy != null) { this.defaultMethodExpressionHandler.setRoleHierarchy(roleHierarchy); } AuthenticationTrustResolver trustResolver = getSingleBeanOrNull( AuthenticationTrustResolver.class); if (trustResolver != null) { this.defaultMethodExpressionHandler.setTrustResolver(trustResolver); } GrantedAuthorityDefaults grantedAuthorityDefaults = getSingleBeanOrNull( GrantedAuthorityDefaults.class); if (grantedAuthorityDefaults != null) { this.defaultMethodExpressionHandler.setDefaultRolePrefix( grantedAuthorityDefaults.getRolePrefix()); } } private <T> T getSingleBeanOrNull(Class<T> type) { String[] beanNamesForType = this.context.getBeanNamesForType(type); if (beanNamesForType == null || beanNamesForType.length != 1) { return null; } return this.context.getBean(beanNamesForType[0], type); } private void initializeMethodSecurityInterceptor() throws Exception { if(this.methodSecurityInterceptor == null) { return; } this.methodSecurityInterceptor.setAuthenticationManager(authenticationManager()); } /** * Provide a custom {@link AfterInvocationManager} for the default implementation of * {@link #methodSecurityInterceptor()}. The default is null if pre post is not * enabled. Otherwise, it returns a {@link AfterInvocationProviderManager}. * * <p> * Subclasses should override this method to provide a custom * {@link AfterInvocationManager} * </p> * * @return */ protected AfterInvocationManager afterInvocationManager() { if (prePostEnabled()) { AfterInvocationProviderManager invocationProviderManager = new AfterInvocationProviderManager(); ExpressionBasedPostInvocationAdvice postAdvice = new ExpressionBasedPostInvocationAdvice( getExpressionHandler()); PostInvocationAdviceProvider postInvocationAdviceProvider = new PostInvocationAdviceProvider( postAdvice); List<AfterInvocationProvider> afterInvocationProviders = new ArrayList<AfterInvocationProvider>(); afterInvocationProviders.add(postInvocationAdviceProvider); invocationProviderManager.setProviders(afterInvocationProviders); return invocationProviderManager; } return null; } /** * Provide a custom {@link RunAsManager} for the default implementation of * {@link #methodSecurityInterceptor()}. The default is null. * * @return */ protected RunAsManager runAsManager() { return null; } /** * Allows subclasses to provide a custom {@link AccessDecisionManager}. The default is * a {@link AffirmativeBased} with the following voters: * * <ul> * <li>{@link PreInvocationAuthorizationAdviceVoter}</li> * <li>{@link RoleVoter}</li> * <li>{@link AuthenticatedVoter}</li> * </ul> * * @return */ protected AccessDecisionManager accessDecisionManager() { List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<AccessDecisionVoter<? extends Object>>(); ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice(); expressionAdvice.setExpressionHandler(getExpressionHandler()); if (prePostEnabled()) { decisionVoters .add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice)); } if (jsr250Enabled()) { decisionVoters.add(new Jsr250Voter()); } decisionVoters.add(new RoleVoter()); decisionVoters.add(new AuthenticatedVoter()); return new AffirmativeBased(decisionVoters); } /** * Provide a {@link MethodSecurityExpressionHandler} that is registered with the * {@link ExpressionBasedPreInvocationAdvice}. The default is * {@link DefaultMethodSecurityExpressionHandler} which optionally will Autowire an * {@link AuthenticationTrustResolver}. * * <p> * Subclasses may override this method to provide a custom * {@link MethodSecurityExpressionHandler} * </p> * * @return */ protected MethodSecurityExpressionHandler createExpressionHandler() { return defaultMethodExpressionHandler; } /** * Gets the {@link MethodSecurityExpressionHandler} or creates it using * {@link #expressionHandler}. * * @return a non {@code null} {@link MethodSecurityExpressionHandler} */ protected final MethodSecurityExpressionHandler getExpressionHandler() { if (expressionHandler == null) { expressionHandler = createExpressionHandler(); } return expressionHandler; } /** * Provides a custom {@link MethodSecurityMetadataSource} that is registered with the * {@link #methodSecurityMetadataSource()}. Default is null. * * @return a custom {@link MethodSecurityMetadataSource} that is registered with the * {@link #methodSecurityMetadataSource()} */ protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() { return null; } /** * Allows providing a custom {@link AuthenticationManager}. The default is to use any * authentication mechanisms registered by * {@link #configure(AuthenticationManagerBuilder)}. If * {@link #configure(AuthenticationManagerBuilder)} was not overridden, then an * {@link AuthenticationManager} is attempted to be autowired by type. * * @return */ protected AuthenticationManager authenticationManager() throws Exception { if (authenticationManager == null) { DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor .postProcess(new DefaultAuthenticationEventPublisher()); auth = new AuthenticationManagerBuilder(objectPostProcessor); auth.authenticationEventPublisher(eventPublisher); configure(auth); if (disableAuthenticationRegistry) { authenticationManager = getAuthenticationConfiguration() .getAuthenticationManager(); } else { authenticationManager = auth.build(); } } return authenticationManager; } /** * Sub classes can override this method to register different types of authentication. * If not overridden, {@link #configure(AuthenticationManagerBuilder)} will attempt to * autowire by type. * * @param auth the {@link AuthenticationManagerBuilder} used to register different * authentication mechanisms for the global method security. * @throws Exception */ protected void configure(AuthenticationManagerBuilder auth) throws Exception { this.disableAuthenticationRegistry = true; } /** * Provides the default {@link MethodSecurityMetadataSource} that will be used. It * creates a {@link DelegatingMethodSecurityMetadataSource} based upon * {@link #customMethodSecurityMetadataSource()} and the attributes on * {@link EnableGlobalMethodSecurity}. * * @return */ @Bean public MethodSecurityMetadataSource methodSecurityMetadataSource() { List<MethodSecurityMetadataSource> sources = new ArrayList<MethodSecurityMetadataSource>(); ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory( getExpressionHandler()); MethodSecurityMetadataSource customMethodSecurityMetadataSource = customMethodSecurityMetadataSource(); if (customMethodSecurityMetadataSource != null) { sources.add(customMethodSecurityMetadataSource); } if (prePostEnabled()) { sources.add(new PrePostAnnotationSecurityMetadataSource(attributeFactory)); } if (securedEnabled()) { sources.add(new SecuredAnnotationSecurityMetadataSource()); } if (jsr250Enabled()) { GrantedAuthorityDefaults grantedAuthorityDefaults = getSingleBeanOrNull(GrantedAuthorityDefaults.class); if (grantedAuthorityDefaults != null) { this.jsr250MethodSecurityMetadataSource.setDefaultRolePrefix( grantedAuthorityDefaults.getRolePrefix()); } sources.add(jsr250MethodSecurityMetadataSource); } return new DelegatingMethodSecurityMetadataSource(sources); } /** * Creates the {@link PreInvocationAuthorizationAdvice} to be used. The default is * {@link ExpressionBasedPreInvocationAdvice}. * * @return */ @Bean public PreInvocationAuthorizationAdvice preInvocationAuthorizationAdvice() { ExpressionBasedPreInvocationAdvice preInvocationAdvice = new ExpressionBasedPreInvocationAdvice(); preInvocationAdvice.setExpressionHandler(getExpressionHandler()); return preInvocationAdvice; } /** * Obtains the attributes from {@link EnableGlobalMethodSecurity} if this class was * imported using the {@link EnableGlobalMethodSecurity} annotation. */ public final void setImportMetadata(AnnotationMetadata importMetadata) { Map<String, Object> annotationAttributes = importMetadata .getAnnotationAttributes(EnableGlobalMethodSecurity.class.getName()); enableMethodSecurity = AnnotationAttributes.fromMap(annotationAttributes); } @Autowired(required = false) public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) { this.objectPostProcessor = objectPostProcessor; this.defaultMethodExpressionHandler = objectPostProcessor .postProcess(defaultMethodExpressionHandler); } @Autowired(required = false) public void setJsr250MethodSecurityMetadataSource( Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource) { this.jsr250MethodSecurityMetadataSource = jsr250MethodSecurityMetadataSource; } @Autowired(required = false) public void setMethodSecurityExpressionHandler( List<MethodSecurityExpressionHandler> handlers) { if (handlers.size() != 1) { logger.debug("Not autowiring MethodSecurityExpressionHandler since size != 1. Got " + handlers); return; } this.expressionHandler = handlers.get(0); } @Autowired public void setApplicationContext(ApplicationContext context) { this.context = context; } private AuthenticationConfiguration getAuthenticationConfiguration() { return context.getBean(AuthenticationConfiguration.class); } private boolean prePostEnabled() { return enableMethodSecurity().getBoolean("prePostEnabled"); } private boolean securedEnabled() { return enableMethodSecurity().getBoolean("securedEnabled"); } private boolean jsr250Enabled() { return enableMethodSecurity().getBoolean("jsr250Enabled"); } private int order() { return (Integer) enableMethodSecurity().get("order"); } private boolean isAspectJ() { return enableMethodSecurity().getEnum("mode") == AdviceMode.ASPECTJ; } private AnnotationAttributes enableMethodSecurity() { if (enableMethodSecurity == null) { // if it is null look at this instance (i.e. a subclass was used) EnableGlobalMethodSecurity methodSecurityAnnotation = AnnotationUtils .findAnnotation(getClass(), EnableGlobalMethodSecurity.class); Assert.notNull(methodSecurityAnnotation, EnableGlobalMethodSecurity.class.getName() + " is required"); Map<String, Object> methodSecurityAttrs = AnnotationUtils .getAnnotationAttributes(methodSecurityAnnotation); this.enableMethodSecurity = AnnotationAttributes.fromMap(methodSecurityAttrs); } return this.enableMethodSecurity; } }