diff --git a/web/src/main/java/org/apache/shiro/web/filter/authz/SslFilter.java b/web/src/main/java/org/apache/shiro/web/filter/authz/SslFilter.java index 3a6ab7a331..d85bb23e69 100644 --- a/web/src/main/java/org/apache/shiro/web/filter/authz/SslFilter.java +++ b/web/src/main/java/org/apache/shiro/web/filter/authz/SslFilter.java @@ -20,6 +20,7 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; /** * Filter which requires a request to be over SSL. Access is allowed if the request is received on the configured @@ -30,21 +31,46 @@ * The {@link #getPort() port} property defaults to {@code 443} and also additionally guarantees that the * request scheme is always 'https' (except for port 80, which retains the 'http' scheme). *

- * Example config: + * In addition the filter allows enabling HTTP Strict Transport Security (HSTS). + * This feature is opt-in and disabled by default. If enabled HSTS + * will prevent any communications from being sent over HTTP to the + * specified domain and will instead send all communications over HTTPS. + *

+ * Warning: Use this setting only if you plan to enable SSL on every path. + *

+ * Example configs: *
  * [urls]
  * /secure/path/** = ssl
  * 
- * + * with HSTS enabled + *
+ * [main]
+ * ssl.hsts.enabled = true
+ * [urls]
+ * /** = ssl
+ * 
* @since 1.0 + * @see HTTP Strict Transport Security (HSTS) */ public class SslFilter extends PortFilter { public static final int DEFAULT_HTTPS_PORT = 443; public static final String HTTPS_SCHEME = "https"; + + private HSTS hsts; public SslFilter() { setPort(DEFAULT_HTTPS_PORT); + this.hsts = new HSTS(); + } + + public HSTS getHsts() { + return hsts; + } + + public void setHsts(HSTS hsts) { + this.hsts = hsts; } @Override @@ -73,4 +99,58 @@ protected String getScheme(String requestScheme, int port) { protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return super.isAccessAllowed(request, response, mappedValue) && request.isSecure(); } + + @Override + protected void postHandle(ServletRequest request, ServletResponse response) throws Exception { + if (hsts.enabled) { + StringBuilder directives = new StringBuilder(64); + directives.append("max-age=").append(hsts.getMaxAge()); + if (hsts.includeSubDomains) { + directives.append("; includeSubDomains"); + } + HttpServletResponse resp = (HttpServletResponse) response; + resp.addHeader("Strict-Transport-Security", directives.toString()); + } + } + + public class HSTS { + + static final boolean DEFAULT_ENABLED = false; + public static final int DEFAULT_EXPIRE_TIME = 31536000; // approx. one year in seconds + public static final boolean DEFAULT_INCLUDE_SUB_DOMAINS = false; + + private boolean enabled; + private int maxAge; + private boolean includeSubDomains; + + public HSTS() { + this.maxAge = DEFAULT_EXPIRE_TIME; + this.includeSubDomains = DEFAULT_INCLUDE_SUB_DOMAINS; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getMaxAge() { + return maxAge; + } + + public void setMaxAge(int maxAge) { + this.maxAge = maxAge; + } + + public boolean isIncludeSubDomains() { + return includeSubDomains; + } + + public void setIncludeSubDomains(boolean includeSubDomains) { + this.includeSubDomains = includeSubDomains; + } + + } }