@Configuration @PropertySource(CoreSpringConfig.PROPERTIES) public class WebappSpringConfig { @Autowired private Environment environment; /** * 'springSecurityFilterChain' is Magic ID and it is required by Spring Security DelegatingFilterProxy in web.xml * * We cannot use @EnableWebSecurity with WebSecurityConfigurerAdapter because they allows only single * SecurityFilterChain fore whole web application, but we have 2 chains * - Active Directory/LDAP backed for Admin web application * - Properties backed for REST endpoints */ @Bean(name = "springSecurityFilterChain") public FilterChainProxy springSecurityFilterChain() throws Exception { List filterChains = new ArrayList<>(); filterChains.add(restSecurityChain("/api/**")); filterChains.add(adminSecurityChain("/admin/**")); FilterChainProxy filterChainProxy = new FilterChainProxy(filterChains); return filterChainProxy; } public SecurityFilterChain restSecurityChain(String pattern) throws Exception { //Don't create session for REST caller SecurityContextPersistenceFilter persistenceFilter = buildPersistenceFilter(false); AuthenticationManager authenticationManager = buildRestAuthenticationManager(); BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint(); entryPoint.setRealmName("Rest Realm"); BasicAuthenticationFilter basicFilter = new BasicAuthenticationFilter(authenticationManager, entryPoint); basicFilter.afterPropertiesSet(); AccessDecisionManager accessDecisionManager = buildAccessDecisionManager(); AnonymousAuthenticationFilter anonymousFilter = buildAnonymousAuthenticationFilter("ROLE_ANONYMOUS"); FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor(); securityInterceptor.setAuthenticationManager(authenticationManager); securityInterceptor.setAccessDecisionManager(accessDecisionManager); LinkedHashMap> requestMap = new LinkedHashMap<>(); requestMap.put(AntMatcher("/api/xxx/**"), SecurityConfig.createList("ROLE_XXX_REST")); requestMap.put(AntMatcher("/api/yyy/**"), SecurityConfig.createList("ROLE_YYY_REST")); DefaultFilterInvocationSecurityMetadataSource metadataSource = new DefaultFilterInvocationSecurityMetadataSource( requestMap); securityInterceptor.setSecurityMetadataSource(metadataSource); securityInterceptor.afterPropertiesSet(); //Send 403 directly to REST caller - Http403ForbiddenEntryPoint ExceptionTranslationFilter exceptionFilter = new ExceptionTranslationFilter(entryPoint); return new DefaultSecurityFilterChain(AntMatcher(pattern), persistenceFilter, basicFilter, anonymousFilter, exceptionFilter, securityInterceptor); } /** * We store XXX and YYY credentials in confidential.properties */ private AuthenticationManager buildRestAuthenticationManager() throws Exception { Collection users = new ArrayList<>(); String xxxUsername = environment.getRequiredProperty("authenticate.xxx.username"); String xxxPassword = environment.getRequiredProperty("authenticate.xxx.password"); users.add(new User(xxxUsername, xxxPassword, Arrays.asList(new SimpleGrantedAuthority("ROLE_XXX_REST")))); String yyyUsername = environment.getRequiredProperty("authenticate.yyy.username"); String yyyPassword = environment.getRequiredProperty("authenticate.yyy.password"); users.add(new User(yyyUsername, yyyPassword, Arrays.asList(new SimpleGrantedAuthority("ROLE_YYY_REST")))); InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager(users); DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(userDetailsService); authenticationProvider.afterPropertiesSet(); AuthenticationManager authenticationManager = new ProviderManager( Arrays.asList((AuthenticationProvider) authenticationProvider)); return authenticationManager; } private AccessDecisionManager buildAccessDecisionManager() { List voters = new ArrayList<>(); voters.add(new RoleVoter());//RoleHierarchyVoter //voters.add(new WebExpressionVoter()); AccessDecisionManager accessDecisionManager = new AffirmativeBased(voters); return accessDecisionManager; } public SecurityFilterChain adminSecurityChain(String pattern) throws Exception { //Allow session for Interactive users SecurityContextPersistenceFilter persistenceFilter = buildPersistenceFilter(true); //Interactive user can logout SimpleUrlLogoutSuccessHandler successHandler = new SimpleUrlLogoutSuccessHandler(); successHandler.setDefaultTargetUrl("/"); SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler(); logoutHandler.setClearAuthentication(true); logoutHandler.setInvalidateHttpSession(true); LogoutFilter logoutFilter = new LogoutFilter(successHandler, logoutHandler); //Build AuthenticationManager from provided LDAP AuthenticationProvider AuthenticationManager authenticationManager = new ProviderManager(Arrays.asList(adminAuthenticationProvider())); BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint(); entryPoint.setRealmName("Admin Realm"); BasicAuthenticationFilter basicFilter = new BasicAuthenticationFilter(authenticationManager, entryPoint); basicFilter.afterPropertiesSet(); AnonymousAuthenticationFilter anonymousFilter = buildAnonymousAuthenticationFilter("ROLE_ANONYMOUS"); AccessDecisionManager accessDecisionManager = buildAccessDecisionManager(); FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor(); securityInterceptor.setAuthenticationManager(authenticationManager); securityInterceptor.setAccessDecisionManager(accessDecisionManager); //Single role to cover whole application LinkedHashMap> requestMap = new LinkedHashMap<>(); requestMap.put(AntMatcher("/admin/**"), SecurityConfig.createList("ROLE_ADMIN")); DefaultFilterInvocationSecurityMetadataSource metadataSource = new DefaultFilterInvocationSecurityMetadataSource( requestMap); securityInterceptor.setSecurityMetadataSource(metadataSource); securityInterceptor.afterPropertiesSet(); //Send BASIC challenge to interactive users ExceptionTranslationFilter exceptionFilter = new ExceptionTranslationFilter(entryPoint); //org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@5d4971e //org.springframework.security.web.context.SecurityContextPersistenceFilter@39f7e368 //org.springframework.security.web.header.HeaderWriterFilter@392a028f //org.springframework.security.web.authentication.logout.LogoutFilter@7dab317 //org.springframework.security.web.authentication.www.BasicAuthenticationFilter@240b6882 //org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5c72a676 //org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@14728025 //org.springframework.security.web.authentication.AnonymousAuthenticationFilter@480d358f //org.springframework.security.web.session.SessionManagementFilter@f72ebc1 //org.springframework.security.web.access.ExceptionTranslationFilter@6f4809f6 //org.springframework.security.web.access.intercept.FilterSecurityInterceptor@3dbe15bc return new DefaultSecurityFilterChain(AntMatcher(pattern), persistenceFilter, logoutFilter, basicFilter, anonymousFilter, exceptionFilter, securityInterceptor); } private AuthenticationProvider adminAuthenticationProvider() { String domain = environment.getRequiredProperty("ldap.domain"); String url = environment.getRequiredProperty("ldap.url"); ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(domain, url); provider.setAuthoritiesMapper(new SimpleAuthorityMapper()); //add ROLE_ prefix return provider; } public static RequestMatcher AntMatcher(String pattern) { if (pattern == null || pattern.isEmpty()) { throw new IllegalArgumentException("Null or empty pattern"); } return new AntPathRequestMatcher(pattern); } private AnonymousAuthenticationFilter buildAnonymousAuthenticationFilter(String anonymousRole) { AnonymousAuthenticationFilter filter = new AnonymousAuthenticationFilter("anonymous-key", "AnonymousUser", AuthorityUtils.createAuthorityList(anonymousRole)); return filter; } private SecurityContextPersistenceFilter buildPersistenceFilter(boolean allowSessions) throws ServletException { HttpSessionSecurityContextRepository repository = new HttpSessionSecurityContextRepository(); repository.setAllowSessionCreation(allowSessions); SecurityContextPersistenceFilter persistenceFilter = new SecurityContextPersistenceFilter(repository); persistenceFilter.afterPropertiesSet(); return persistenceFilter; } }