package org.libresonic.player.security; import org.apache.commons.lang3.StringUtils; import org.libresonic.player.service.JWTSecurityService; import org.libresonic.player.service.SecurityService; import org.libresonic.player.service.SettingsService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.SecurityProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.authentication.configurers.GlobalAuthenticationConfigurerAdapter; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @Configuration @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class GlobalSecurityConfig extends GlobalAuthenticationConfigurerAdapter { private static Logger logger = LoggerFactory.getLogger(GlobalSecurityConfig.class); static final String FAILURE_URL = "/login?error=1"; @Autowired private SecurityService securityService; @Autowired private CsrfSecurityRequestMatcher csrfSecurityRequestMatcher; @Autowired LoginFailureLogger loginFailureLogger; @Autowired SettingsService settingsService; @Autowired LibresonicUserDetailsContextMapper libresonicUserDetailsContextMapper; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { if (settingsService.isLdapEnabled()) { auth.ldapAuthentication() .contextSource() .managerDn(settingsService.getLdapManagerDn()) .managerPassword(settingsService.getLdapManagerPassword()) .url(settingsService.getLdapUrl()) .and() .userSearchFilter(settingsService.getLdapSearchFilter()) .userDetailsContextMapper(libresonicUserDetailsContextMapper); } auth.userDetailsService(securityService); String jwtKey = settingsService.getJWTKey(); if(StringUtils.isBlank(jwtKey)) { logger.warn("Generating new jwt key"); jwtKey = JWTSecurityService.generateKey(); settingsService.setJWTKey(jwtKey); settingsService.save(); } auth.authenticationProvider(new JWTAuthenticationProvider(jwtKey)); } @Configuration @Order(1) public class ExtSecurityConfiguration extends WebSecurityConfigurerAdapter { public ExtSecurityConfiguration() { super(true); } @Bean(name = "jwtAuthenticationFilter") public JWTRequestParameterProcessingFilter jwtAuthFilter() throws Exception { return new JWTRequestParameterProcessingFilter(authenticationManager(), FAILURE_URL); } @Override protected void configure(HttpSecurity http) throws Exception { http = http.addFilter(new WebAsyncManagerIntegrationFilter()); http = http.addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class); http .antMatcher("/ext/**") .csrf().requireCsrfProtectionMatcher(csrfSecurityRequestMatcher).and() .headers().frameOptions().sameOrigin().and() .authorizeRequests() .antMatchers("/ext/stream/**", "/ext/coverArt*", "/ext/share/**", "/ext/hls/**") .hasAnyRole("TEMP", "USER").and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .exceptionHandling().and() .securityContext().and() .requestCache().and() .anonymous().and() .servletApi(); } } @Configuration @Order(2) public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { RESTRequestParameterProcessingFilter restAuthenticationFilter = new RESTRequestParameterProcessingFilter(); restAuthenticationFilter.setAuthenticationManager(authenticationManagerBean()); restAuthenticationFilter.setSecurityService(securityService); restAuthenticationFilter.setLoginFailureLogger(loginFailureLogger); http = http.addFilterBefore(restAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); http .csrf() .requireCsrfProtectionMatcher(csrfSecurityRequestMatcher) .and().headers() .frameOptions() .sameOrigin() .and().authorizeRequests() .antMatchers("/recover*", "/accessDenied*", "/style/**", "/icons/**", "/flash/**", "/script/**", "/sonos/**", "/crossdomain.xml", "/login", "/error") .permitAll() .antMatchers("/personalSettings*", "/passwordSettings*", "/playerSettings*", "/shareSettings*", "/passwordSettings*") .hasRole("SETTINGS") .antMatchers("/generalSettings*", "/advancedSettings*", "/userSettings*", "/musicFolderSettings*", "/databaseSettings*") .hasRole("ADMIN") .antMatchers("/deletePlaylist*", "/savePlaylist*", "/db*") .hasRole("PLAYLIST") .antMatchers("/download*") .hasRole("DOWNLOAD") .antMatchers("/upload*") .hasRole("UPLOAD") .antMatchers("/createShare*") .hasRole("SHARE") .antMatchers("/changeCoverArt*", "/editTags*") .hasRole("COVERART") .antMatchers("/setMusicFileInfo*") .hasRole("COMMENT") .antMatchers("/podcastReceiverAdmin*") .hasRole("PODCAST") .antMatchers("/**") .hasRole("USER") .anyRequest().authenticated() .and().formLogin() .loginPage("/login") .permitAll() .defaultSuccessUrl("/index", true) .failureUrl(FAILURE_URL) .usernameParameter("j_username") .passwordParameter("j_password") // see http://docs.spring.io/spring-security/site/docs/3.2.4.RELEASE/reference/htmlsingle/#csrf-logout .and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout", "GET")).logoutSuccessUrl( "/login?logout") .and().rememberMe().key("libresonic"); } } }