diff --git a/java-tools/OsmAndServer/build.gradle b/java-tools/OsmAndServer/build.gradle index f0bca21e0..539549db1 100644 --- a/java-tools/OsmAndServer/build.gradle +++ b/java-tools/OsmAndServer/build.gradle @@ -64,12 +64,14 @@ dependencies { implementation "org.springframework.boot:spring-boot-starter-security" implementation "org.springframework.boot:spring-boot-starter-thymeleaf" implementation "org.springframework.boot:spring-boot-starter-data-jpa" - implementation "org.springframework.boot:spring-boot-starter-data-redis" implementation "org.springframework.boot:spring-boot-starter-oauth2-client" implementation "org.javassist:javassist:3.25.0-GA" implementation "javax.xml.bind:jaxb-api:2.3.1" implementation "javax.activation:activation:1.1" implementation "org.glassfish.jaxb:jaxb-runtime:2.3.5" + + implementation 'org.springframework.session:spring-session-data-redis' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'com.sendgrid:sendgrid-java:4.2.1' implementation group: 'net.sf.trove4j', name: 'trove4j', version: '3.0.3' diff --git a/java-tools/OsmAndServer/src/main/java/net/osmand/server/WebSecurityConfiguration.java b/java-tools/OsmAndServer/src/main/java/net/osmand/server/WebSecurityConfiguration.java index b955f7e49..102475836 100644 --- a/java-tools/OsmAndServer/src/main/java/net/osmand/server/WebSecurityConfiguration.java +++ b/java-tools/OsmAndServer/src/main/java/net/osmand/server/WebSecurityConfiguration.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; import javax.servlet.http.HttpServletRequest; import javax.sql.DataSource; @@ -18,24 +19,24 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.jdbc.DataSourceBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.*; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.env.Profiles; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpResponse; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -57,6 +58,8 @@ import org.springframework.security.web.csrf.CookieCsrfTokenRepository; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; +import org.springframework.session.MapSessionRepository; +import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestTemplate; import org.springframework.web.cors.CorsConfiguration; @@ -79,11 +82,19 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { public static final String ROLE_PRO_USER = "ROLE_PRO_USER"; public static final String ROLE_ADMIN = "ROLE_ADMIN"; public static final String ROLE_USER = "ROLE_USER"; + private static final int SESSION_TTL_SECONDS = 3600 * 24 * 30; @Value("${admin.api-oauth2-url}") private String adminOauth2Url; - - @Autowired + + @Value("${spring.session.redisHost}") + private String redisHost; + + @Value("${spring.session.redisPort}") + private String redisPort; + + + @Autowired protected PremiumUsersRepository usersRepository; @Autowired @@ -108,7 +119,37 @@ public PremiumUserDevice getUserDevice() { return userDevice; } } - + + private boolean isRedisAvailable() { + return redisHost != null && !redisHost.isEmpty() && redisPort != null && !redisPort.isEmpty(); + } + + @Bean + @ConditionalOnExpression("T(org.springframework.util.StringUtils).isEmpty('${REDIS_HOST:}') && T(org.springframework.util.StringUtils).isEmpty('${REDIS_PORT:}')") + public MapSessionRepository mapSessionRepository() { + if (!isRedisAvailable()) { + LOG.warn("Redis is not configured, falling back to MapSessionRepository."); + MapSessionRepository repository = new MapSessionRepository(new ConcurrentHashMap<>()); + repository.setDefaultMaxInactiveInterval(SESSION_TTL_SECONDS); + return repository; + } + LOG.info("Redis configuration is detected, skipping MapSessionRepository."); + return null; + } + + @Configuration + @ConditionalOnExpression("!T(org.springframework.util.StringUtils).isEmpty('${REDIS_HOST:}') && !T(org.springframework.util.StringUtils).isEmpty('${REDIS_PORT:}')") + @EnableRedisHttpSession(maxInactiveIntervalInSeconds = SESSION_TTL_SECONDS) + public class ConditionalRedisConfig { + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + LettuceConnectionFactory factory = new LettuceConnectionFactory(redisHost, Integer.parseInt(redisPort)); + factory.afterPropertiesSet(); + LOG.info("Redis connection established: " + factory.getConnection().ping()); + return factory; + } + } @Override protected void configure(final AuthenticationManagerBuilder auth) throws Exception { @@ -133,8 +174,13 @@ public UserDetails loadUserByUsername(String username) { @Override protected void configure(HttpSecurity http) throws Exception { - // http.csrf().disable().antMatcher("/**"); - // 1. CSRF + http.sessionManagement() + .maximumSessions(1) + .and() + .sessionCreationPolicy(SessionCreationPolicy.ALWAYS); + + + // 1. CSRF Set enabledMethods = new TreeSet<>( Arrays.asList("GET", "HEAD", "TRACE", "OPTIONS", "POST", "DELETE")); http.csrf().requireCsrfProtectionMatcher(new RequestMatcher() { @@ -172,7 +218,6 @@ public boolean matches(HttpServletRequest request) { mapLogin.setForceHttps(true); } http.exceptionHandling().defaultAuthenticationEntryPointFor(mapLogin, new AntPathRequestMatcher("/mapapi/**")); -// http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)); http.rememberMe().tokenValiditySeconds(3600*24*14); http.logout().deleteCookies("JSESSIONID"). logoutSuccessUrl("/").logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll(); diff --git a/java-tools/OsmAndServer/src/main/java/net/osmand/server/api/repo/PremiumUserDevicesRepository.java b/java-tools/OsmAndServer/src/main/java/net/osmand/server/api/repo/PremiumUserDevicesRepository.java index 67c4ff312..5148d8ab4 100644 --- a/java-tools/OsmAndServer/src/main/java/net/osmand/server/api/repo/PremiumUserDevicesRepository.java +++ b/java-tools/OsmAndServer/src/main/java/net/osmand/server/api/repo/PremiumUserDevicesRepository.java @@ -1,5 +1,7 @@ package net.osmand.server.api.repo; +import java.io.Serial; +import java.io.Serializable; import java.util.Date; import java.util.List; @@ -29,7 +31,9 @@ public interface PremiumUserDevicesRepository extends JpaRepository files = new ArrayList<>(); public List tempFiles = new ArrayList<>(); } diff --git a/java-tools/OsmAndServer/src/main/resources/application.yml b/java-tools/OsmAndServer/src/main/resources/application.yml index e320d01ef..a3b79b5c2 100644 --- a/java-tools/OsmAndServer/src/main/resources/application.yml +++ b/java-tools/OsmAndServer/src/main/resources/application.yml @@ -75,6 +75,9 @@ management: exposure: include: "*" spring: + session: + redisPort: ${REDIS_PORT:} + redisHost: ${REDIS_HOST:} servlet: multipart: max-file-size: ${SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE:50MB}