/* * Copyright (C) 2008-2020, Juick * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.juick.config; import com.juick.SignatureManager; import com.juick.service.UserService; import com.juick.service.security.HTTPSignatureAuthenticationFilter; import com.juick.service.security.HashParamAuthenticationFilter; import com.juick.service.security.JuickUserDetailsService; import com.juick.service.security.entities.JuickUser; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.authentication.NullRememberMeServices; import org.springframework.security.web.authentication.RememberMeServices; import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import javax.annotation.Resource; import javax.inject.Inject; import java.util.Arrays; import java.util.Collections; import java.util.concurrent.TimeUnit; /** * Created by aalexeev on 11/21/16. */ @EnableWebSecurity public class SecurityConfig { @Resource private UserService userService; private static final String COOKIE_NAME = "juick-remember-me"; @Bean public UserDetailsService userDetailsService() { return new JuickUserDetailsService(userService); } @Bean static CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Collections.singletonList("*")); configuration.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "OPTIONS", "DELETE")); configuration.setAllowedHeaders(Collections.singletonList("*")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/api/**", configuration); source.registerCorsConfiguration("/u/**", configuration); source.registerCorsConfiguration("/n/**", configuration); return source; } @Configuration @Order(1) public static class ApiConfig extends WebSecurityConfigurerAdapter { @Value("${auth_remember_me_key:secret}") private String rememberMeKey; @Resource private UserService userService; @Resource private SignatureManager signatureManager; ApiConfig() { super(true); } @Bean RememberMeServices apiTokenServices() { return new NullRememberMeServices(); } @Bean public HashParamAuthenticationFilter apiAuthenticationFilter() { return new HashParamAuthenticationFilter(userService, apiTokenServices()); } @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/api/**") .addFilterBefore(apiAuthenticationFilter(), BasicAuthenticationFilter.class) .addFilterBefore(new HTTPSignatureAuthenticationFilter(signatureManager, userService), BasicAuthenticationFilter.class) .authorizeRequests() .antMatchers(HttpMethod.OPTIONS).permitAll() .antMatchers("/api/", "/api/messages", "/api/avatar", "/api/messages/discussions", "/api/users", "/api/thread", "/api/tags", "/api/tlgmbtwbhk", "/api/fbwbhk", "/api/skypebotendpoint", "/api/_fblogin", "/api/_vklogin", "/api/_tglogin", "/api/_google", "/api/_applelogin", "/api/signup", "/api/inbox", "/api/events", "/api/info/**", "/api/nodeinfo/2.0").permitAll() .anyRequest().hasRole("USER") .and() .anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY) .and() .httpBasic().authenticationEntryPoint(juickAuthenticationEntryPoint()) .and().cors().configurationSource(corsConfigurationSource()) .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and().exceptionHandling().authenticationEntryPoint(juickAuthenticationEntryPoint()) .and() .rememberMe() .alwaysRemember(true) .tokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(6 * 30)) .rememberMeServices(apiTokenServices()) .key(rememberMeKey) .and() .headers().defaultsDisabled().cacheControl(); } @Bean public AuthenticationEntryPoint juickAuthenticationEntryPoint() { var entryPoint = new BasicAuthenticationEntryPoint(); entryPoint.setRealmName("Juick"); return entryPoint; } } @Configuration public static class WebConfig extends WebSecurityConfigurerAdapter { @Value("${auth_remember_me_key:secret}") private String rememberMeKey; @Value("${web_domain:localhost}") private String webDomain; @Resource private UserService userService; @Inject private UserDetailsService userDetailsService; @Bean @Qualifier("www") public HashParamAuthenticationFilter wwwAuthenticationFilter() { return new HashParamAuthenticationFilter(userService, hashCookieServices()); } @Bean @Qualifier("www") public RememberMeServices hashCookieServices() { TokenBasedRememberMeServices services = new TokenBasedRememberMeServices( rememberMeKey, userDetailsService); services.setCookieName(COOKIE_NAME); services.setCookieDomain(webDomain); services.setAlwaysRemember(true); services.setTokenValiditySeconds(6 * 30 * 24 * 3600); services.setUseSecureCookie(false); // TODO set true if https is supports return services; } @Override protected void configure(HttpSecurity http) throws Exception { http .addFilterBefore(wwwAuthenticationFilter(), BasicAuthenticationFilter.class) .authorizeRequests() .antMatchers("/settings", "/pm/**", "/**/bl", "/_twitter", "/post", "/post2", "/comment") .authenticated() .antMatchers("/actuator/**").hasRole("ADMIN") .anyRequest().permitAll() .and() .anonymous().principal(JuickUser.ANONYMOUS_USER).authorities(JuickUser.ANONYMOUS_AUTHORITY) .and().cors().configurationSource(corsConfigurationSource()) .and().sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .invalidSessionUrl("/") .and() .logout() .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) .invalidateHttpSession(true) .logoutUrl("/logout") .logoutSuccessUrl("/") .deleteCookies("hash", COOKIE_NAME) .and() .formLogin() .loginPage("/login") .permitAll() .defaultSuccessUrl("/") .loginProcessingUrl("/login") .usernameParameter("username") .passwordParameter("password") .failureUrl("/login?error=1") .and() .rememberMe() .rememberMeCookieDomain(webDomain).key(rememberMeKey) .rememberMeServices(hashCookieServices()) .and() .csrf().disable() .headers().defaultsDisabled().cacheControl(); } } }