org.springframework.security.web.header.writers.HstsHeaderWriter Maven / Gradle / Ivy
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed 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.
*/
package org.springframework.security.web.header.writers;
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.header.HeaderWriter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* Provides support for HTTP Strict Transport
* Security (HSTS).
*
*
* By default the expiration is one year and subdomains will be included. This can be
* customized using {@link #setMaxAgeInSeconds(long)} and
* {@link #setIncludeSubDomains(boolean)} respectively.
*
*
*
* Since section 7.2 states
* that HSTS Host MUST NOT include the STS header in HTTP responses, the default behavior
* is that the "Strict-Transport-Security" will only be added when
* {@link HttpServletRequest#isSecure()} returns {@code true} . At times this may need to
* be customized. For example, in some situations where SSL termination is used, something
* else may be used to determine if SSL was used. For these circumstances,
* {@link #setRequestMatcher(RequestMatcher)} can be invoked with a custom
* {@link RequestMatcher}.
*
*
* @author Rob Winch
* @since 3.2
*/
public final class HstsHeaderWriter implements HeaderWriter {
private static final long DEFAULT_MAX_AGE_SECONDS = 31536000;
private static final String HSTS_HEADER_NAME = "Strict-Transport-Security";
private final Log logger = LogFactory.getLog(getClass());
private RequestMatcher requestMatcher;
private long maxAgeInSeconds;
private boolean includeSubDomains;
private String hstsHeaderValue;
/**
* Creates a new instance
*
* @param requestMatcher maps to {@link #setRequestMatcher(RequestMatcher)}
* @param maxAgeInSeconds maps to {@link #setMaxAgeInSeconds(long)}
* @param includeSubDomains maps to {@link #setIncludeSubDomains(boolean)}
*/
public HstsHeaderWriter(RequestMatcher requestMatcher, long maxAgeInSeconds,
boolean includeSubDomains) {
this.requestMatcher = requestMatcher;
this.maxAgeInSeconds = maxAgeInSeconds;
this.includeSubDomains = includeSubDomains;
updateHstsHeaderValue();
}
/**
* Creates a new instance
*
* @param maxAgeInSeconds maps to {@link #setMaxAgeInSeconds(long)}
* @param includeSubDomains maps to {@link #setIncludeSubDomains(boolean)}
*/
public HstsHeaderWriter(long maxAgeInSeconds, boolean includeSubDomains) {
this(new SecureRequestMatcher(), maxAgeInSeconds, includeSubDomains);
}
/**
* Creates a new instance
*
* @param maxAgeInSeconds maps to {@link #setMaxAgeInSeconds(long)}
*/
public HstsHeaderWriter(long maxAgeInSeconds) {
this(new SecureRequestMatcher(), maxAgeInSeconds, true);
}
/**
* Creates a new instance
*
* @param includeSubDomains maps to {@link #setIncludeSubDomains(boolean)}
*/
public HstsHeaderWriter(boolean includeSubDomains) {
this(new SecureRequestMatcher(), DEFAULT_MAX_AGE_SECONDS, includeSubDomains);
}
/**
* Creates a new instance
*/
public HstsHeaderWriter() {
this(DEFAULT_MAX_AGE_SECONDS);
}
/*
* (non-Javadoc)
*
* @see org.springframework.security.web.headers.HeaderWriter#writeHeaders(javax
* .servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
if (this.requestMatcher.matches(request)) {
response.setHeader(HSTS_HEADER_NAME, this.hstsHeaderValue);
}
else if (this.logger.isDebugEnabled()) {
this.logger
.debug("Not injecting HSTS header since it did not match the requestMatcher "
+ this.requestMatcher);
}
}
/**
* Sets the {@link RequestMatcher} used to determine if the
* "Strict-Transport-Security" should be added. If true the header is added, else the
* header is not added. By default the header is added when
* {@link HttpServletRequest#isSecure()} returns true.
*
* @param requestMatcher the {@link RequestMatcher} to use.
* @throws IllegalArgumentException if {@link RequestMatcher} is null
*/
public void setRequestMatcher(RequestMatcher requestMatcher) {
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
this.requestMatcher = requestMatcher;
}
/**
*
* Sets the value (in seconds) for the max-age directive of the
* Strict-Transport-Security header. The default is one year.
*
*
*
* This instructs browsers how long to remember to keep this domain as a known HSTS
* Host. See Section
* 6.1.1 for additional details.
*
*
* @param maxAgeInSeconds the maximum amount of time (in seconds) to consider this
* domain as a known HSTS Host.
* @throws IllegalArgumentException if maxAgeInSeconds is negative
*/
public void setMaxAgeInSeconds(long maxAgeInSeconds) {
if (maxAgeInSeconds < 0) {
throw new IllegalArgumentException(
"maxAgeInSeconds must be non-negative. Got " + maxAgeInSeconds);
}
this.maxAgeInSeconds = maxAgeInSeconds;
updateHstsHeaderValue();
}
/**
*
* If true, subdomains should be considered HSTS Hosts too. The default is true.
*
*
*
* See Section 6.1.2
* for additional details.
*
*
* @param includeSubDomains true to include subdomains, else false
*/
public void setIncludeSubDomains(boolean includeSubDomains) {
this.includeSubDomains = includeSubDomains;
updateHstsHeaderValue();
}
private void updateHstsHeaderValue() {
String headerValue = "max-age=" + this.maxAgeInSeconds;
if (this.includeSubDomains) {
headerValue += " ; includeSubDomains";
}
this.hstsHeaderValue = headerValue;
}
private static final class SecureRequestMatcher implements RequestMatcher {
public boolean matches(HttpServletRequest request) {
return request.isSecure();
}
}
}