View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.shiro.web.filter.authz;
20  
21  import javax.servlet.ServletRequest;
22  import javax.servlet.ServletResponse;
23  import javax.servlet.http.HttpServletResponse;
24  
25  /**
26   * Filter which requires a request to be over SSL.  Access is allowed if the request is received on the configured
27   * server {@link #setPort(int) port} <em>and</em> the
28   * {@code request.}{@link javax.servlet.ServletRequest#isSecure() isSecure()}.  If either condition is {@code false},
29   * the filter chain will not continue.
30   * <p/>
31   * The {@link #getPort() port} property defaults to {@code 443} and also additionally guarantees that the
32   * request scheme is always 'https' (except for port 80, which retains the 'http' scheme).
33   * <p/>
34   * In addition the filter allows enabling HTTP Strict Transport Security (HSTS).
35   * This feature is opt-in and disabled by default. If enabled HSTS
36   * will prevent <b>any</b> communications from being sent over HTTP to the 
37   * specified domain and will instead send all communications over HTTPS.
38   * </p>
39   * The {@link #getMaxAge() maxAge} property defaults {@code 31536000}, and 
40   * {@link #isIncludeSubDomains includeSubDomains} is {@code false}.
41   * </p>
42   * <b>Warning:</b> Use this setting with care and only if you plan to enable 
43   * SSL on every path.
44   * </p>
45   * Example configs:
46   * <pre>
47   * [urls]
48   * /secure/path/** = ssl
49   * </pre>
50   * with HSTS enabled
51   * <pre>
52   * [main]
53   * ssl.hsts.enabled = true
54   * [urls]
55   * /** = ssl
56   * </pre>
57   * @since 1.0
58   * @see <a href="https://tools.ietf.org/html/rfc6797">HTTP Strict Transport Security (HSTS)</a>
59   */
60  public class SslFilter extends PortFilter {
61  
62      public static final int DEFAULT_HTTPS_PORT = 443;
63      public static final String HTTPS_SCHEME = "https";
64      
65      private HSTS hsts;
66  
67      public SslFilter() {
68          setPort(DEFAULT_HTTPS_PORT);
69          this.hsts = new HSTS();
70      }
71  
72      public HSTS getHsts() {
73          return hsts;
74      }
75  
76      public void setHsts(HSTS hsts) {
77          this.hsts = hsts;
78      }
79  
80      @Override
81      protected String getScheme(String requestScheme, int port) {
82          if (port == DEFAULT_HTTP_PORT) {
83              return PortFilter.HTTP_SCHEME;
84          } else {
85              return HTTPS_SCHEME;
86          }
87      }
88  
89      /**
90       * Retains the parent method's port-matching behavior but additionally guarantees that the
91       *{@code ServletRequest.}{@link javax.servlet.ServletRequest#isSecure() isSecure()}.  If the port does not match or
92       * the request is not secure, access is denied.
93       *
94       * @param request     the incoming {@code ServletRequest}
95       * @param response    the outgoing {@code ServletResponse} - ignored in this implementation
96       * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings - ignored by this implementation.
97       * @return {@code true} if the request is received on an expected SSL port and the
98       * {@code request.}{@link javax.servlet.ServletRequest#isSecure() isSecure()}, {@code false} otherwise.
99       * @throws Exception if the call to {@code super.isAccessAllowed} throws an exception.
100      * @since 1.2
101      */
102     @Override
103     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
104         return super.isAccessAllowed(request, response, mappedValue) && request.isSecure();
105     }
106 
107     /**
108      * If HTTP Strict Transport Security (HSTS) is enabled the HTTP header
109      * will be written, otherwise this method does nothing.
110      * @param request the incoming {@code ServletRequest}
111      * @param response the outgoing {@code ServletResponse}
112      */
113     @Override
114     protected void postHandle(ServletRequest request, ServletResponse response)  {
115         if (hsts.isEnabled()) {
116             StringBuilder directives = new StringBuilder(64)
117                     .append("max-age=").append(hsts.getMaxAge());
118             
119             if (hsts.isIncludeSubDomains()) {
120                 directives.append("; includeSubDomains");
121             }
122             
123             HttpServletResponse resp = (HttpServletResponse) response;
124             resp.addHeader(HSTS.HTTP_HEADER, directives.toString());
125         }
126     }
127     
128     /**
129      * Helper class for HTTP Strict Transport Security (HSTS)
130      */
131     public class HSTS {
132         
133         public static final String HTTP_HEADER = "Strict-Transport-Security";
134         
135         public static final boolean DEFAULT_ENABLED = false;
136         public static final int DEFAULT_MAX_AGE = 31536000; // approx. one year in seconds
137         public static final boolean DEFAULT_INCLUDE_SUB_DOMAINS = false;
138         
139         private boolean enabled;
140         private int maxAge;
141         private boolean includeSubDomains;
142         
143         public HSTS() {
144             this.enabled = DEFAULT_ENABLED;
145             this.maxAge = DEFAULT_MAX_AGE;
146             this.includeSubDomains = DEFAULT_INCLUDE_SUB_DOMAINS;
147         }
148 
149         public boolean isEnabled() {
150             return enabled;
151         }
152 
153         public void setEnabled(boolean enabled) {
154             this.enabled = enabled;
155         }
156 
157         public int getMaxAge() {
158             return maxAge;
159         }
160 
161         public void setMaxAge(int maxAge) {
162             this.maxAge = maxAge;
163         }
164 
165         public boolean isIncludeSubDomains() {
166             return includeSubDomains;
167         }
168 
169         public void setIncludeSubDomains(boolean includeSubDomains) {
170             this.includeSubDomains = includeSubDomains;
171         }
172     }
173 }