/* * Copyright 2014-2015 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.xd.dirt.web.config; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.access.channel.ChannelProcessingFilter; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.session.ExpiringSession; import org.springframework.session.MapSessionRepository; import org.springframework.session.SessionRepository; import org.springframework.session.web.http.HeaderHttpSessionStrategy; import org.springframework.session.web.http.SessionRepositoryFilter; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.accept.ContentNegotiationStrategy; /** * Setup Spring Security for the http endpoints of the application. * * @author Gunnar Hillert * @author Eric Bottard */ @Configuration @ConditionalOnProperty("security.basic.enabled") @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { public static final Pattern AUTHORIZATION_RULE; static { String methodsRegex = StringUtils.arrayToDelimitedString(HttpMethod.values(), "|"); AUTHORIZATION_RULE = Pattern .compile("(" + methodsRegex + ")\\s+(.+)\\s+=>\\s+(.+)"); } @Bean public SessionRepository<ExpiringSession> sessionRepository() { return new MapSessionRepository(); } @Autowired private ContentNegotiationStrategy contentNegotiationStrategy; @Value("${security.basic.realm}") private String realm; @Autowired private AuthorizationConfig config; @Bean public AuthorizationConfig config() { return new AuthorizationConfig(); } @Override protected void configure(HttpSecurity http) throws Exception { final RequestMatcher textHtmlMatcher = new MediaTypeRequestMatcher( contentNegotiationStrategy, MediaType.TEXT_HTML); final String loginPage = "/admin-ui/#/login"; final BasicAuthenticationEntryPoint basicAuthenticationEntryPoint = new BasicAuthenticationEntryPoint(); basicAuthenticationEntryPoint.setRealmName(realm); basicAuthenticationEntryPoint.afterPropertiesSet(); ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry security = http .csrf() .disable() .authorizeRequests() .antMatchers("/") .authenticated() .antMatchers("/admin-ui/**").permitAll() .antMatchers("/authenticate").permitAll() .antMatchers("/security/info").permitAll() .antMatchers("/assets/**").permitAll(); security = configureSimpleSecurity(security); security.and() .formLogin().loginPage(loginPage) .loginProcessingUrl("/admin-ui/login") .defaultSuccessUrl("/admin-ui/").permitAll() .and().logout().logoutUrl("/admin-ui/logout").logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()) .permitAll() .and().httpBasic() .and().exceptionHandling() .defaultAuthenticationEntryPointFor( new LoginUrlAuthenticationEntryPoint(loginPage), textHtmlMatcher) .defaultAuthenticationEntryPointFor(basicAuthenticationEntryPoint, AnyRequestMatcher.INSTANCE); security.anyRequest().denyAll(); final SessionRepositoryFilter<ExpiringSession> sessionRepositoryFilter = new SessionRepositoryFilter<ExpiringSession>( sessionRepository()); sessionRepositoryFilter .setHttpSessionStrategy(new HeaderHttpSessionStrategy()); http.addFilterBefore(sessionRepositoryFilter, ChannelProcessingFilter.class).csrf().disable(); http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); } /** * Read the configuration for "simple" (that is, not ACL based) security and apply it. */ private ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry configureSimpleSecurity( ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry security) { for (String rule : config.getRules()) { Matcher matcher = AUTHORIZATION_RULE.matcher(rule); Assert.isTrue(matcher.matches(), String.format( "Unable to parse security rule [%s], expected format is 'HTTP_METHOD ANT_PATTERN => SECURITY_ATTRIBUTE(S)'", rule)); HttpMethod method = HttpMethod.valueOf(matcher.group(1)); String urlPattern = matcher.group(2); String attribute = matcher.group(3); security = security.antMatchers(method, urlPattern).access(attribute); } return security; } /** * Holds configuration for the authorization aspects of security. * * @author Eric Bottard */ @ConfigurationProperties(prefix = "xd.security.authorization") public static class AuthorizationConfig { private List<String> rules = new ArrayList<String>(); public List<String> getRules() { return rules; } public void setRules(List<String> rules) { this.rules = rules; } } }