diff --git a/airsonic-main/src/main/java/org/airsonic/player/security/GlobalSecurityConfig.java b/airsonic-main/src/main/java/org/airsonic/player/security/GlobalSecurityConfig.java index b6dba32c..3785a3ac 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/security/GlobalSecurityConfig.java +++ b/airsonic-main/src/main/java/org/airsonic/player/security/GlobalSecurityConfig.java @@ -33,6 +33,8 @@ public class GlobalSecurityConfig extends GlobalAuthenticationConfigurerAdapter static final String FAILURE_URL = "/login?error=1"; + static final String DEVELOPMENT_REMEMBER_ME_KEY = "airsonic"; + @Autowired private SecurityService securityService; @@ -125,6 +127,32 @@ public class GlobalSecurityConfig extends GlobalAuthenticationConfigurerAdapter restAuthenticationFilter.setEventPublisher(eventPublisher); http = http.addFilterBefore(restAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); + // Try to load the 'remember me' key. + // + // Note that using a fixed key compromises security as perfect + // forward secrecy is not guaranteed anymore. + // + // An external entity can then re-use our authentication cookies before + // the expiration time, or even, given enough time, recover the password + // from the MD5 hash. + // + // See: https://docs.spring.io/spring-security/site/docs/3.0.x/reference/remember-me.html + + String rememberMeKey = settingsService.getRememberMeKey(); + boolean development = settingsService.isDevelopmentMode(); + if (StringUtils.isBlank(rememberMeKey) && !development) { + // ...if it is empty, generate a random key on startup (default). + logger.debug("Generating a new ephemeral 'remember me' key in a secure way."); + rememberMeKey = generateRememberMeKey(); + } else if (StringUtils.isBlank(rememberMeKey) && development) { + // ...if we are in development mode, we can use a fixed key. + logger.warn("Using a fixed 'remember me' key because we're in development mode, this is INSECURE."); + rememberMeKey = DEVELOPMENT_REMEMBER_ME_KEY; + } else { + // ...otherwise, use the custom key directly. + logger.info("Using a fixed 'remember me' key from system properties, this is insecure."); + } + http .csrf() .requireCsrfProtectionMatcher(csrfSecurityRequestMatcher) @@ -169,7 +197,7 @@ public class GlobalSecurityConfig extends GlobalAuthenticationConfigurerAdapter // 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(generateRememberMeKey()); + .and().rememberMe().key(rememberMeKey); } } diff --git a/airsonic-main/src/main/java/org/airsonic/player/service/SettingsService.java b/airsonic-main/src/main/java/org/airsonic/player/service/SettingsService.java index 027c5e79..83bd907e 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/service/SettingsService.java +++ b/airsonic-main/src/main/java/org/airsonic/player/service/SettingsService.java @@ -107,6 +107,7 @@ public class SettingsService { private static final String KEY_SONOS_SERVICE_NAME = "SonosServiceName"; private static final String KEY_SONOS_SERVICE_ID = "SonosServiceId"; private static final String KEY_JWT_KEY = "JWTKey"; + private static final String KEY_REMEMBER_ME_KEY = "RememberMeKey"; private static final String KEY_SMTP_SERVER = "SmtpServer"; private static final String KEY_SMTP_ENCRYPTION = "SmtpEncryption"; @@ -794,6 +795,27 @@ public class SettingsService { setString(KEY_MEDIA_LIBRARY_STATISTICS, statistics.format()); } + /** + * Returns whether we are running in Development mode. + * + * @return true if we are in Development mode. + */ + public boolean isDevelopmentMode() { + return System.getProperty("airsonic.development") != null; + } + + /** + * Returns the custom 'remember me' key used for generating authentication tokens. + * + * @return The 'remember me' key. + */ + public String getRememberMeKey() { + String key = null; + if (StringUtils.isBlank(key)) key = getString(KEY_REMEMBER_ME_KEY, null); + if (StringUtils.isBlank(key)) key = System.getProperty("airsonic.rememberMeKey"); + return key; + } + /** * Returns the locale (for language, date format etc). *