Merge pull request #66 from mbien/spring_renovations

Spring Renovations
diff --git a/app/pom.xml b/app/pom.xml
index 81e5c8f..9a067cf 100644
--- a/app/pom.xml
+++ b/app/pom.xml
@@ -56,8 +56,8 @@
         <maven-antrun.version>1.0b3</maven-antrun.version>
         <rome.version>1.13.1</rome.version>
         <slf4j.version>1.7.26</slf4j.version>
-        <spring.version>4.1.4.RELEASE</spring.version>
-        <spring.security.version>3.2.5.RELEASE</spring.security.version>
+        <spring.version>5.2.7.RELEASE</spring.version>
+        <spring.security.version>5.3.3.RELEASE</spring.security.version>
         <struts.version>2.5.22</struts.version>
         <velocity.version>1.7</velocity.version>
         <webjars.version>1.5</webjars.version>
@@ -215,6 +215,16 @@
             <groupId>org.apache.struts</groupId>
             <artifactId>struts2-spring-plugin</artifactId>
             <version>${struts.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-beans</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-core</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <dependency>
diff --git a/app/src/main/java/org/apache/roller/weblogger/pojos/User.java b/app/src/main/java/org/apache/roller/weblogger/pojos/User.java
index bde443f..881f8ee 100644
--- a/app/src/main/java/org/apache/roller/weblogger/pojos/User.java
+++ b/app/src/main/java/org/apache/roller/weblogger/pojos/User.java
@@ -25,10 +25,10 @@
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
 import org.apache.roller.weblogger.WebloggerException;
-import org.apache.roller.weblogger.config.WebloggerConfig;
 import org.apache.roller.util.UUIDGenerator;
 import org.apache.roller.weblogger.business.WebloggerFactory;
-import org.apache.roller.weblogger.util.Utilities;
+import org.apache.roller.weblogger.ui.core.RollerContext;
+import org.springframework.security.crypto.password.PasswordEncoder;
 
 
 /**
@@ -115,15 +115,9 @@
      *
      * @param newPassword The new password to be set.
      */
-    public void resetPassword(String newPassword) throws WebloggerException {
-        
-        String encrypt = WebloggerConfig.getProperty("passwds.encryption.enabled");
-        String algorithm = WebloggerConfig.getProperty("passwds.encryption.algorithm");
-        if (Boolean.valueOf(encrypt)) {
-            setPassword(Utilities.encodePassword(newPassword, algorithm));
-        } else {
-            setPassword(newPassword);
-        }
+    public void resetPassword(String newPassword) {
+        PasswordEncoder encoder = RollerContext.getPasswordEncoder();
+        setPassword(encoder.encode(newPassword));
     }
 
     /**
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/core/RollerContext.java b/app/src/main/java/org/apache/roller/weblogger/ui/core/RollerContext.java
index 3946e75..fb5ec25 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/core/RollerContext.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/core/RollerContext.java
@@ -20,6 +20,8 @@
 
 import java.io.File;
 import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Properties;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletContextEvent;
@@ -30,9 +32,6 @@
 import org.springframework.security.authentication.ProviderManager;
 import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
 import org.springframework.security.core.userdetails.UserCache;
-import org.springframework.security.authentication.encoding.Md5PasswordEncoder;
-import org.springframework.security.authentication.encoding.PasswordEncoder;
-import org.springframework.security.authentication.encoding.ShaPasswordEncoder;
 import org.springframework.security.authentication.RememberMeAuthenticationProvider;
 import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
 import org.apache.commons.logging.Log;
@@ -50,6 +49,10 @@
 import org.apache.velocity.runtime.RuntimeSingleton;
 import org.springframework.beans.factory.NoSuchBeanDefinitionException;
 import org.springframework.context.ApplicationContext;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
 import org.springframework.web.context.ContextLoaderListener;
 import org.springframework.web.context.support.WebApplicationContextUtils;
 
@@ -63,6 +66,7 @@
     private static Log log = LogFactory.getLog(RollerContext.class);
 
     private static ServletContext servletContext = null;
+    private static DelegatingPasswordEncoder encoder;
 
 
     public RollerContext() {
@@ -89,6 +93,9 @@
         return servletContext;
     }
 
+    public static PasswordEncoder getPasswordEncoder() {
+        return encoder;
+    }
 
     /**
      * Responds to app-init event and triggers startup procedures.
@@ -251,27 +258,14 @@
             }
         }
 
-        String encryptPasswords = WebloggerConfig.getProperty("passwds.encryption.enabled");
-        boolean doEncrypt = Boolean.valueOf(encryptPasswords);
+        encoder = createPasswordEncoder();
 
         String daoBeanName = "org.springframework.security.authentication.dao.DaoAuthenticationProvider#0";
 
         // for LDAP-only authentication, no daoBeanName (i.e., UserDetailsService) may be provided in security.xml.
-        if (doEncrypt && ctx.containsBean(daoBeanName)) {
+        if (ctx.containsBean(daoBeanName)) {
             DaoAuthenticationProvider provider = (DaoAuthenticationProvider) ctx.getBean(daoBeanName);
-            String algorithm = WebloggerConfig.getProperty("passwds.encryption.algorithm");
-            PasswordEncoder encoder = null;
-            if (algorithm.equalsIgnoreCase("SHA")) {
-                encoder = new ShaPasswordEncoder();
-            } else if (algorithm.equalsIgnoreCase("MD5")) {
-                encoder = new Md5PasswordEncoder();
-            } else {
-                log.error("Encryption algorithm '" + algorithm + "' not supported, disabling encryption.");
-            }
-            if (encoder != null) {
-                provider.setPasswordEncoder(encoder);
-                log.info("Password Encryption Algorithm set to '" + algorithm + "'");
-            }
+            provider.setPasswordEncoder(encoder);
         }
 
         if (WebloggerConfig.getBooleanProperty("securelogin.enabled")) {
@@ -281,6 +275,58 @@
         }
     }
 
+    @SuppressWarnings("deprecation")
+    private DelegatingPasswordEncoder createPasswordEncoder() {
+
+        Map<String, PasswordEncoder> encoders = new HashMap<>();
+
+        // outdated digest encoder used for lazy upgrades from pws encoded by old roller versions.
+        String migrateFrom = WebloggerConfig.getProperty("passwds.encryption.lazyUpgradeFrom");
+        
+        if(migrateFrom == null || migrateFrom.isEmpty()) {
+            log.debug("lazy pw upgrade disabled");
+        } else if (migrateFrom.equals("plaintext")) {
+            encoders.put(null, org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
+        } else if (migrateFrom.equals("MD5")) {
+            encoders.put(null, new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
+        } else if (migrateFrom.equals("SHA")) {
+            encoders.put(null, new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
+        } else {
+            throw new RuntimeException("passwds.encryption.lazyUpgradeFrom="+migrateFrom+" is no valid encoding to upgrade from.");
+        }
+        
+        // supported encoders
+        encoders.put("bcrypt", new BCryptPasswordEncoder());
+        encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
+        // requires bouncy castle impl
+//        encoders.put("scrypt", new SCryptPasswordEncoder());
+//        encoders.put("argon2", new Argon2PasswordEncoder());
+
+        // just for testing
+        encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
+        
+        String algorithm = WebloggerConfig.getProperty("passwds.encryption.algorithm");
+        
+        if (WebloggerConfig.getBooleanProperty("passwds.encryption.enabled")) {
+            
+            if ("SHA".equals(algorithm) || "MD5".equals(algorithm)) {
+                throw new RuntimeException("passwds.encryption.algorithm="+algorithm+" is outdated,"
+                        + " please set passwds.encryption.algorithm to 'bcrypt' for automatic lazy upgrade.");
+            }
+            
+            if (!encoders.containsKey(algorithm)) {
+                throw new RuntimeException("passwds.encryption.algorithm="+algorithm+" is not supported.");
+            }
+        } else {
+            log.warn("New passwords are stored in plain text!");
+            algorithm = "noop";
+        }
+        
+        log.info("Password Encryption Algorithm set to '" + algorithm + "'");
+        
+        return new DelegatingPasswordEncoder(algorithm, encoders);
+    }
+
 
     /**
      * Flush user from any caches maintained by security system.
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/SpringFirewallExceptionFilter.java b/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/SpringFirewallExceptionFilter.java
new file mode 100644
index 0000000..2ff028c
--- /dev/null
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/core/filters/SpringFirewallExceptionFilter.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  The ASF licenses this file to You
+ * under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.  For additional information regarding
+ * copyright in this work, please see the NOTICE file in the top level
+ * directory of this distribution.
+ */
+
+package org.apache.roller.weblogger.ui.core.filters;
+
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.security.web.firewall.RequestRejectedException;
+
+/**
+ * <p>The spring default firewall ({@link org.springframework.security.web.firewall.StrictHttpFirewall}) 
+ * is throwing exceptions if it decides to block a request. For example double slashes (//) in the request are
+ * interpreted as non-normalized URL and rejected by throwing RequestRejectedExceptions.
+ * Those exceptions are caught by the server and cause 500 errors which isn't very nice behavior.</p>
+ * 
+ * <p>The most straightforward way to handle this seems to be a servlet filter.</p>
+ * 
+ * @see org.springframework.security.web.firewall.StrictHttpFirewall
+ */
+public class SpringFirewallExceptionFilter implements Filter {
+    
+    private final static Log log = LogFactory.getLog(SpringFirewallExceptionFilter.class);
+
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        try {
+            chain.doFilter(request, response);
+        } catch (RequestRejectedException ex) {
+            
+            HttpServletRequest req = (HttpServletRequest) request;
+            HttpServletResponse resp = (HttpServletResponse) response;
+            
+            // url seems to be dangerous -> log & 404
+            log.warn("request rejected: " + req.getRequestURL() + " cause: " + ex.getMessage());
+            resp.sendError(HttpServletResponse.SC_NOT_FOUND);
+            
+        }
+    }
+
+    @Override
+    public void destroy() {}
+
+    @Override
+    public void init(FilterConfig filterConfig) throws ServletException {}
+    
+
+}
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/core/security/RollerRememberMeAuthenticationProvider.java b/app/src/main/java/org/apache/roller/weblogger/ui/core/security/RollerRememberMeAuthenticationProvider.java
index 7c8b60c..78d50f6 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/core/security/RollerRememberMeAuthenticationProvider.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/core/security/RollerRememberMeAuthenticationProvider.java
@@ -25,20 +25,20 @@
 
 
 public class RollerRememberMeAuthenticationProvider extends RememberMeAuthenticationProvider {
-    private static final Log log = LogFactory.getLog(RollerRememberMeServices.class);
+    private static final Log log = LogFactory.getLog(RollerRememberMeAuthenticationProvider.class);
 
 
     public RollerRememberMeAuthenticationProvider() {
+        
+        super(WebloggerConfig.getProperty("rememberme.key", "springRocks"));
+        
         log.debug("initializing: RollerRememberMeAuthenticationProvider");
 
-        String key = WebloggerConfig.getProperty("rememberme.key", "springRocks");
-
-        if ("springRocks".equals(key)) {
+        if (WebloggerConfig.getBooleanProperty("rememberme.enabled") && "springRocks".equals(getKey())) {
             throw new RuntimeException(
                 "If remember-me is to be enabled, rememberme.key must be specified in the roller " +
                 "properties file. Make sure it is a secret and make sure it is NOT springRocks");
         }
-        setKey(key);
 
         log.debug("initialized: RollerRememberMeAuthenticationProvider with key: " + getKey());
     }
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/core/security/RollerRememberMeServices.java b/app/src/main/java/org/apache/roller/weblogger/ui/core/security/RollerRememberMeServices.java
index ca765af..af1afc2 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/core/security/RollerRememberMeServices.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/core/security/RollerRememberMeServices.java
@@ -22,6 +22,7 @@
 import org.apache.commons.logging.LogFactory;
 import org.apache.roller.weblogger.config.AuthMethod;
 import org.apache.roller.weblogger.config.WebloggerConfig;
+import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.crypto.codec.Hex;
 import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
 
@@ -33,17 +34,17 @@
     private static final Log log = LogFactory.getLog(RollerRememberMeServices.class);
 
 
-    public RollerRememberMeServices() {
+    public RollerRememberMeServices(UserDetailsService userDetailsService) {
+        
+        super(WebloggerConfig.getProperty("rememberme.key", "springRocks"), userDetailsService);
+        
         log.debug("initializing: RollerRememberMeServices");
 
-        String key = WebloggerConfig.getProperty("rememberme.key", "springRocks");
-
-        if ("springRocks".equals(key)) {
+        if (WebloggerConfig.getBooleanProperty("rememberme.enabled") && "springRocks".equals(getKey())) {
             throw new RuntimeException(
                 "If remember-me is to be enabled, rememberme.key must be specified in the roller " +
                     "properties file. Make sure it is a secret and make sure it is NOT springRocks");
         }
-        setKey(key);
 
         log.debug("initialized: RollerRememberMeServices with key");
     }
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/UserEdit.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/UserEdit.java
index 73107b9..15ae410 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/UserEdit.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/admin/UserEdit.java
@@ -39,7 +39,6 @@
 import org.apache.roller.weblogger.pojos.WeblogPermission;
 import org.apache.roller.weblogger.ui.struts2.core.Register;
 import org.apache.roller.weblogger.ui.struts2.util.UIAction;
-import org.apache.struts2.convention.annotation.AllowedMethods;
 import org.apache.struts2.interceptor.validation.SkipValidation;
 
 
@@ -159,21 +158,13 @@
             // User.password does not allow null, so generate one
             if (authMethod.equals(AuthMethod.OPENID) ||
                     (authMethod.equals(AuthMethod.DB_OPENID) && !StringUtils.isEmpty(bean.getOpenIdUrl()))) {
-                try {
-                    String randomString = RandomStringUtils.randomAlphanumeric(255);
-                    user.resetPassword(randomString);
-                } catch (WebloggerException e) {
-                    addMessage("yourProfile.passwordResetError");
-                }
+                String randomString = RandomStringUtils.randomAlphanumeric(255);
+                user.resetPassword(randomString);
             }
 
             // reset password if set
             if (!StringUtils.isEmpty(getBean().getPassword())) {
-                try {
-                    user.resetPassword(getBean().getPassword());
-                } catch (WebloggerException e) {
-                    addMessage("yourProfile.passwordResetError");
-                }
+                user.resetPassword(getBean().getPassword());
             }
 
             try {
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/core/Install.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/core/Install.java
index 7ad0522..3552395 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/core/Install.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/core/Install.java
@@ -31,8 +31,7 @@
 import org.apache.roller.weblogger.business.startup.WebloggerStartup;
 import org.apache.roller.weblogger.config.WebloggerConfig;
 import org.apache.roller.weblogger.ui.struts2.util.UIAction;
-import org.apache.struts2.convention.annotation.AllowedMethods;
-import org.springframework.beans.factory.access.BootstrapException;
+import org.springframework.beans.FatalBeanException;
 
 
 /**
@@ -173,8 +172,8 @@
             log.info("EXITING - Bootstrap successful, forwarding to Roller");
             return SUCCESS;
 
-        } catch (BootstrapException ex) {
-            log.error("BootstrapException", ex);
+        } catch (FatalBeanException ex) {
+            log.error("FatalBeanException", ex);
             rootCauseException = ex;
         } catch (WebloggerException ex) {
             log.error("WebloggerException", ex);
diff --git a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/core/Profile.java b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/core/Profile.java
index 8f6e60f..198ce2b 100644
--- a/app/src/main/java/org/apache/roller/weblogger/ui/struts2/core/Profile.java
+++ b/app/src/main/java/org/apache/roller/weblogger/ui/struts2/core/Profile.java
@@ -28,7 +28,6 @@
 import org.apache.roller.weblogger.config.WebloggerConfig;
 import org.apache.roller.weblogger.pojos.User;
 import org.apache.roller.weblogger.ui.struts2.util.UIAction;
-import org.apache.struts2.convention.annotation.AllowedMethods;
 import org.apache.struts2.interceptor.validation.SkipValidation;
 
 
@@ -105,21 +104,13 @@
             if (authMethod.equals(AuthMethod.OPENID) ||
                     (authMethod.equals(AuthMethod.DB_OPENID) && !StringUtils.isEmpty(bean.getOpenIdUrl()))) {
                 String randomString = RandomStringUtils.randomAlphanumeric(255);
-                try {
-                    existingUser.resetPassword(randomString);
-                } catch (WebloggerException e) {
-                    addMessage("yourProfile.passwordResetError");
-                }
+                existingUser.resetPassword(randomString);
             }
 
             // If user set both password and passwordConfirm then reset password
             if (!StringUtils.isEmpty(getBean().getPasswordText()) &&
                     !StringUtils.isEmpty(getBean().getPasswordConfirm())) {
-                try {
-                    existingUser.resetPassword(getBean().getPasswordText());
-                } catch (WebloggerException e) {
-                    addMessage("yourProfile.passwordResetError");
-                }
+                existingUser.resetPassword(getBean().getPasswordText());
             }
 
             try {
diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomHandler.java b/app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomHandler.java
index f397461..f6a427e 100644
--- a/app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomHandler.java
+++ b/app/src/main/java/org/apache/roller/weblogger/webservices/atomprotocol/RollerAtomHandler.java
@@ -29,7 +29,6 @@
 import org.apache.roller.weblogger.pojos.User;
 import org.apache.roller.weblogger.pojos.WeblogEntry;
 import org.apache.roller.weblogger.pojos.Weblog;
-import org.apache.roller.weblogger.util.Utilities;
 import org.apache.roller.weblogger.util.WSSEUtilities;
 import com.rometools.propono.atom.common.AtomService;
 import com.rometools.propono.atom.server.AtomException;
@@ -48,6 +47,7 @@
 import org.apache.roller.weblogger.config.WebloggerConfig;
 import org.apache.roller.weblogger.config.WebloggerRuntimeConfig;
 import org.apache.roller.weblogger.pojos.WeblogPermission;
+import org.apache.roller.weblogger.ui.core.RollerContext;
 
 
 /**
@@ -448,7 +448,6 @@
     public String authenticateBASIC(HttpServletRequest request) {
         boolean valid = false;
         String userID = null;
-        String password = null;
         try {
             String authHeader = request.getHeader("Authorization");
             if (authHeader != null) {
@@ -462,17 +461,9 @@
                         if (p != -1) {
                             userID = userPass.substring(0, p);
                             User inUser = roller.getUserManager().getUserByUserName(userID);
-                            boolean enabled = inUser.getEnabled();
-                            if (enabled) {
-                                // are passwords encrypted?
-                                String encrypted =
-                                        WebloggerConfig.getProperty("passwds.encryption.enabled");
-                                password = userPass.substring(p+1);
-                                if ("true".equalsIgnoreCase(encrypted)) {
-                                    password = Utilities.encodePassword(password,
-                                            WebloggerConfig.getProperty("passwds.encryption.algorithm"));
-                                }
-                                valid = inUser.getPassword().equals(password);
+                            if (inUser.getEnabled()) {
+                                String password = userPass.substring(p+1);
+                                valid = RollerContext.getPasswordEncoder().matches(password, user.getPassword());
                             }
                         }
                     }
diff --git a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BaseAPIHandler.java b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BaseAPIHandler.java
index d509fed..6426924 100644
--- a/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BaseAPIHandler.java
+++ b/app/src/main/java/org/apache/roller/weblogger/webservices/xmlrpc/BaseAPIHandler.java
@@ -25,15 +25,14 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.xmlrpc.XmlRpcException;
-import org.apache.roller.weblogger.config.WebloggerConfig;
 import org.apache.roller.weblogger.config.WebloggerRuntimeConfig;
 import org.apache.roller.weblogger.business.WebloggerFactory;
 import org.apache.roller.weblogger.business.UserManager;
 import org.apache.roller.weblogger.business.WeblogManager;
 import org.apache.roller.weblogger.pojos.User;
 import org.apache.roller.weblogger.pojos.Weblog;
+import org.apache.roller.weblogger.ui.core.RollerContext;
 import org.apache.roller.weblogger.util.cache.CacheManager;
-import org.apache.roller.weblogger.util.Utilities;
 import org.apache.xmlrpc.common.XmlRpcNotAuthorizedException;
 
 /**
@@ -104,9 +103,9 @@
     //------------------------------------------------------------------------
     /**
      * Returns website, but only if user authenticates and is authorized to edit.
-     * @param blogid   Blogid sent in request (used as website's hanldle)
+     * @param blogid   Blogid sent in request (used as website's handle)
      * @param username Username sent in request
-     * @param password Password sent in requeset
+     * @param password Password sent in request
      */
     protected Weblog validate(String blogid, String username, String password)
     throws Exception {
@@ -116,12 +115,10 @@
         boolean apiEnabled = false;
         boolean weblogFound = false;
         Weblog website = null;
-        User user = null;
         try {
             UserManager userMgr = WebloggerFactory.getWeblogger().getUserManager();
             WeblogManager weblogMgr = WebloggerFactory.getWeblogger().getWeblogManager();
-            user = userMgr.getUserByUserName(username);
-            userEnabled = user.getEnabled();
+            User user = userMgr.getUserByUserName(username);
             
             website = weblogMgr.getWeblogByHandle(blogid);
             if (website != null) {
@@ -132,15 +129,8 @@
             }
             
             if (user != null) {
-                // are passwords encrypted
-                String encrypted =
-                        WebloggerConfig.getProperty("passwds.encryption.enabled");
-                //System.out.print("password was [" + password + "] ");
-                if ("true".equalsIgnoreCase(encrypted)) {
-                    password = Utilities.encodePassword(password,
-                            WebloggerConfig.getProperty("passwds.encryption.algorithm"));
-                }
-                authenticated = password.equals(user.getPassword());
+                userEnabled = user.getEnabled();
+                authenticated = RollerContext.getPasswordEncoder().matches(password, user.getPassword());
             }
         } catch (Exception e) {
             mLogger.error("ERROR internal error validating user", e);
@@ -168,31 +158,21 @@
     /**
      * Returns true if username/password are valid and user is not disabled.
      * @param username Username sent in request
-     * @param password Password sent in requeset
+     * @param password Password sent in request
      */
     protected boolean validateUser(String username, String password)
     throws Exception {
         boolean authenticated = false;
         boolean enabled = false;
         boolean apiEnabled = false;
-        User user = null;
         try {
             
             UserManager userMgr = WebloggerFactory.getWeblogger().getUserManager();
-            user = userMgr.getUserByUserName(username);
+            User user = userMgr.getUserByUserName(username);
             
-            enabled = user.getEnabled();
-            if (enabled) {
-                // are passwords encrypted?
-                String encrypted =
-                        WebloggerConfig.getProperty("passwds.encryption.enabled");
-                //System.out.print("password was [" + password + "] ");
-                if ("true".equalsIgnoreCase(encrypted)) {
-                    password = Utilities.encodePassword(password,
-                            WebloggerConfig.getProperty("passwds.encryption.algorithm"));
-                }
-                //System.out.println("is now [" + password + "]");
-                authenticated = user.getPassword().equals(password);
+            if (user != null) {
+                enabled = user.getEnabled();
+                authenticated = RollerContext.getPasswordEncoder().matches(password, user.getPassword());
                 
                 apiEnabled = WebloggerRuntimeConfig.getBooleanProperty("webservices.enableXmlRpc");
             }
@@ -200,14 +180,14 @@
             mLogger.error("ERROR internal error validating user", e);
         }
         
-        if ( !enabled ) {
-            throw new XmlRpcNotAuthorizedException(USER_DISABLED_MSG);
-        }
-        
         if ( !authenticated ) {
             throw new XmlRpcNotAuthorizedException(AUTHORIZATION_EXCEPTION_MSG);
         }
         
+        if ( !enabled ) {
+            throw new XmlRpcNotAuthorizedException(USER_DISABLED_MSG);
+        }
+        
         if ( !apiEnabled ) {
             throw new XmlRpcNotAuthorizedException(BLOGGERAPI_DISABLED_MSG);
         }        
diff --git a/app/src/main/resources/org/apache/roller/weblogger/config/roller.properties b/app/src/main/resources/org/apache/roller/weblogger/config/roller.properties
index ffbd275..5308ae7 100644
--- a/app/src/main/resources/org/apache/roller/weblogger/config/roller.properties
+++ b/app/src/main/resources/org/apache/roller/weblogger/config/roller.properties
@@ -348,7 +348,11 @@
 
 # Password security settings
 passwds.encryption.enabled=true
-passwds.encryption.algorithm=SHA
+passwds.encryption.algorithm=bcrypt
+
+# Allows lazy upgrade from legacy encodings. Can be plaintext, SHA or MD5 or left empty.
+# Should be set to the value of 'passwds.encryption.algorithm' used on old Roller installations.
+passwds.encryption.lazyUpgradeFrom=SHA
 
 # Role name to global permission action mappings
 role.names=editor,admin
@@ -607,7 +611,7 @@
 # Some folks consider remember-me type functionality to be a security risk
 # If you enable remember me you MUST define a unique secret key that is not 'springRocks'
 rememberme.enabled=false
-rememberme.key=
+rememberme.key=springRocks
 
 # You might want to disable GZIP if your app server already supports it
 compression.gzipResponse.enabled=true
diff --git a/app/src/main/webapp/WEB-INF/security.xml b/app/src/main/webapp/WEB-INF/security.xml
index d3e8fa3..08e7c62 100644
--- a/app/src/main/webapp/WEB-INF/security.xml
+++ b/app/src/main/webapp/WEB-INF/security.xml
@@ -20,15 +20,15 @@
        xmlns:beans="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
-          http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
+          http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
           http://www.springframework.org/schema/security
-          http://www.springframework.org/schema/security/spring-security-3.2.xsd">
+          http://www.springframework.org/schema/security/spring-security-5.3.xsd">
 
     <http pattern="/images/**" security="none"/>
     <http pattern="/scripts/**" security="none"/>
     <http pattern="/styles/**" security="none"/>
 
-    <http auto-config="false" access-decision-manager-ref="accessDecisionManager">
+    <http auto-config="false" access-decision-manager-ref="accessDecisionManager" use-expressions="false">
         <intercept-url pattern="/roller-ui/login-redirect**" access="admin,editor"/>
         <intercept-url pattern="/roller-ui/profile**" access="admin,editor"/>
         <intercept-url pattern="/roller-ui/createWeblog**" access="admin,editor"/>
@@ -38,6 +38,8 @@
         <intercept-url pattern="/rewrite-status*" access="admin"/>
 
         <form-login login-page="/roller-ui/login.rol"
+                    username-parameter="j_username" 
+                    password-parameter="j_password" 
                     authentication-failure-url="/roller-ui/login.rol?error=true"
                     login-processing-url="/roller_j_security_check"/>
 
@@ -45,15 +47,19 @@
                      key="715F2448-3176-11DD-ABC6-9CD955D89593"/>
 
         <custom-filter ref="openidAuthenticationProcessingFilter" position="OPENID_FILTER"/>
+        
+        <!-- roller already uses its own salt based CSRF protection-->
+        <csrf disabled="true"/>
+        
+        <!-- some roller UI (i.e. media file editor) uses iframes -->
+        <headers>
+            <frame-options policy="SAMEORIGIN"/>
+        </headers>
     </http>
 
     <beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
+        <beans:constructor-arg ref="roleVoter"/>
         <beans:property name="allowIfAllAbstainDecisions" value="false"/>
-        <beans:property name="decisionVoters">
-            <beans:list>
-                <beans:ref bean="roleVoter"/>
-            </beans:list>
-        </beans:property>
     </beans:bean>
 
     <beans:bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter">
@@ -78,13 +84,11 @@
 
     <beans:bean id="rollerRememberMeServices"
                 class="org.apache.roller.weblogger.ui.core.security.RollerRememberMeServices">
-        <beans:property name="key" value="ignored"/>
-        <beans:property name="userDetailsService" ref="rollerUserService"/>
+        <beans:constructor-arg name="userDetailsService" ref="rollerUserService"/>
     </beans:bean>
 
     <beans:bean id="rememberMeAuthenticationProvider"
                 class="org.apache.roller.weblogger.ui.core.security.RollerRememberMeAuthenticationProvider">
-        <beans:property name="key" value="ignored"/>
     </beans:bean>
 
     <beans:bean id = "openIDAuthProvider" class="org.springframework.security.openid.OpenIDAuthenticationProvider">
diff --git a/app/src/main/webapp/WEB-INF/web.xml b/app/src/main/webapp/WEB-INF/web.xml
index 3ccd730..27229df 100644
--- a/app/src/main/webapp/WEB-INF/web.xml
+++ b/app/src/main/webapp/WEB-INF/web.xml
@@ -31,6 +31,11 @@
     </filter>
 
     <filter>
+        <filter-name>SpringFirewallExceptionFilter</filter-name>
+        <filter-class>org.apache.roller.weblogger.ui.core.filters.SpringFirewallExceptionFilter</filter-class>
+    </filter>
+
+    <filter>
         <filter-name>PersistenceSessionFilter</filter-name>
         <filter-class>org.apache.roller.weblogger.ui.core.filters.PersistenceSessionFilter</filter-class>
     </filter>
@@ -98,6 +103,14 @@
         <url-pattern>/roller-ui/rendering/trackback/*</url-pattern>
         <dispatcher>FORWARD</dispatcher>
     </filter-mapping>
+    
+    <!-- keep right above spring firewall filter, see source for details -->
+    <filter-mapping>
+        <filter-name>SpringFirewallExceptionFilter</filter-name>
+        <url-pattern>/*</url-pattern>
+        <dispatcher>REQUEST</dispatcher>
+        <dispatcher>FORWARD</dispatcher>
+    </filter-mapping>
 
     <!-- Spring Security filters - controls secure access to different parts of Roller -->
     <filter-mapping>