View Javadoc

1   /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.springframework.security.ui.webapp;
17  
18  import org.springframework.security.AuthenticationException;
19  
20  import org.springframework.security.ui.AuthenticationEntryPoint;
21  
22  import org.springframework.security.util.PortMapper;
23  import org.springframework.security.util.PortMapperImpl;
24  import org.springframework.security.util.PortResolver;
25  import org.springframework.security.util.PortResolverImpl;
26  import org.springframework.security.util.RedirectUrlBuilder;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  import org.springframework.beans.factory.InitializingBean;
32  
33  import org.springframework.util.Assert;
34  
35  import java.io.IOException;
36  
37  import javax.servlet.RequestDispatcher;
38  import javax.servlet.ServletException;
39  import javax.servlet.ServletRequest;
40  import javax.servlet.ServletResponse;
41  import javax.servlet.http.HttpServletRequest;
42  import javax.servlet.http.HttpServletResponse;
43  
44  /**
45   * <p>
46   * Used by the <code>SecurityEnforcementFilter</code> to commence
47   * authentication via the {@link AuthenticationProcessingFilter}. This object
48   * holds the location of the login form, relative to the web app context path,
49   * and is used to commence a redirect to that form.
50   * <p>
51   * By setting the <em>forceHttps</em> property to true, you may configure the
52   * class to force the protocol used for the login form to be <code>HTTPS</code>,
53   * even if the original intercepted request for a resource used the
54   * <code>HTTP</code> protocol. When this happens, after a successful login
55   * (via HTTPS), the original resource will still be accessed as HTTP, via the
56   * original request URL. For the forced HTTPS feature to work, the {@link
57   * PortMapper} is consulted to determine the HTTP:HTTPS pairs.
58   *
59   * @author Ben Alex
60   * @author colin sampaleanu
61   * @author Omri Spector
62   * @version $Id: AuthenticationProcessingFilterEntryPoint.java 2686 2008-03-03 21:57:59Z luke_t $
63   */
64  public class AuthenticationProcessingFilterEntryPoint implements AuthenticationEntryPoint, InitializingBean {
65      //~ Static fields/initializers =====================================================================================
66  
67      private static final Log logger = LogFactory.getLog(AuthenticationProcessingFilterEntryPoint.class);
68  
69      //~ Instance fields ================================================================================================
70  
71      private PortMapper portMapper = new PortMapperImpl();
72  
73      private PortResolver portResolver = new PortResolverImpl();
74  
75      private String loginFormUrl;
76  
77      private boolean forceHttps = false;
78  
79      private boolean serverSideRedirect = false;
80  
81      //~ Methods ========================================================================================================
82  
83      public void afterPropertiesSet() throws Exception {
84          Assert.hasLength(loginFormUrl, "loginFormUrl must be specified");
85          Assert.notNull(portMapper, "portMapper must be specified");
86          Assert.notNull(portResolver, "portResolver must be specified");
87      }
88  
89      /**
90       * Allows subclasses to modify the login form URL that should be applicable for a given request.
91       *
92       * @param request   the request
93       * @param response  the response
94       * @param exception the exception
95       * @return the URL (cannot be null or empty; defaults to {@link #getLoginFormUrl()})
96       */
97      protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response,
98              AuthenticationException exception) {
99  
100         return getLoginFormUrl();
101     }
102 
103     /**
104      * Performs the redirect (or forward) to the login form URL.
105      */
106     public void commence(ServletRequest request, ServletResponse response, AuthenticationException authException)
107             throws IOException, ServletException {
108 
109         HttpServletRequest httpRequest = (HttpServletRequest) request;
110         HttpServletResponse httpResponse = (HttpServletResponse) response;
111 
112         String redirectUrl = null;
113 
114         if (serverSideRedirect) {
115 
116             if (forceHttps && "http".equals(request.getScheme())) {
117                 redirectUrl = buildHttpsRedirectUrlForRequest(httpRequest);
118             }
119 
120             if (redirectUrl == null) {
121                 String loginForm = determineUrlToUseForThisRequest(httpRequest, httpResponse, authException);
122                 
123                 if (logger.isDebugEnabled()) {
124                     logger.debug("Server side forward to: " + loginForm);
125                 }
126 
127                 RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(loginForm);
128 
129                 dispatcher.forward(request, response);
130 
131                 return;
132             }
133         } else {
134             // redirect to login page. Use https if forceHttps true
135 
136             redirectUrl = buildRedirectUrlToLoginPage(httpRequest, httpResponse, authException);
137 
138         }
139 
140         httpResponse.sendRedirect(httpResponse.encodeRedirectURL(redirectUrl));
141     }
142 
143     protected String buildRedirectUrlToLoginPage(HttpServletRequest request, HttpServletResponse response,
144             AuthenticationException authException) {
145 
146         String loginForm = determineUrlToUseForThisRequest(request, response, authException);
147         int serverPort = portResolver.getServerPort(request);
148         String scheme = request.getScheme();
149 
150         RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder();
151 
152         urlBuilder.setScheme(scheme);
153         urlBuilder.setServerName(request.getServerName());
154         urlBuilder.setPort(serverPort);
155         urlBuilder.setContextPath(request.getContextPath());
156         urlBuilder.setPathInfo(loginForm);
157 
158         if (forceHttps && "http".equals(scheme)) {
159             Integer httpsPort = portMapper.lookupHttpsPort(new Integer(serverPort));
160 
161             if (httpsPort != null) {
162                 // Overwrite scheme and port in the redirect URL
163                 urlBuilder.setScheme("https");
164                 urlBuilder.setPort(httpsPort.intValue());
165             } else {
166                 logger.warn("Unable to redirect to HTTPS as no port mapping found for HTTP port " + serverPort);
167             }
168         }
169 
170         return urlBuilder.getUrl();
171     }
172 
173     /**
174      * Builds a URL to redirect the supplied request to HTTPS.
175      */
176     protected String buildHttpsRedirectUrlForRequest(HttpServletRequest request)
177             throws IOException, ServletException {
178 
179         int serverPort = portResolver.getServerPort(request);
180         Integer httpsPort = portMapper.lookupHttpsPort(new Integer(serverPort));
181 
182         if (httpsPort != null) {
183             RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder();
184             urlBuilder.setScheme("https");
185             urlBuilder.setServerName(request.getServerName());
186             urlBuilder.setPort(httpsPort.intValue());
187             urlBuilder.setContextPath(request.getContextPath());
188             urlBuilder.setServletPath(request.getServletPath());
189             urlBuilder.setPathInfo(request.getPathInfo());
190             urlBuilder.setQuery(request.getQueryString());
191 
192             return urlBuilder.getUrl();
193         }
194 
195         // Fall through to server-side forward with warning message
196         logger.warn("Unable to redirect to HTTPS as no port mapping found for HTTP port " + serverPort);
197 
198         return null;
199     }
200 
201     /**
202      * Set to true to force login form access to be via https. If this value is true (the default is false),
203      * and the incoming request for the protected resource which triggered the interceptor was not already
204      * <code>https</code>, then the client will first be redirected to an https URL, even if <tt>serverSideRedirect</tt>
205      * is set to <tt>true</tt>.
206      */
207     public void setForceHttps(boolean forceHttps) {
208         this.forceHttps = forceHttps;
209     }
210 
211     protected boolean isForceHttps() {
212         return forceHttps;
213     }
214 
215     /**
216      * The URL where the <code>AuthenticationProcessingFilter</code> login
217      * page can be found. Should be relative to the web-app context path, and
218      * include a leading <code>/</code>
219      */
220     public void setLoginFormUrl(String loginFormUrl) {
221         this.loginFormUrl = loginFormUrl;
222     }
223 
224     public String getLoginFormUrl() {
225         return loginFormUrl;
226     }
227 
228     public void setPortMapper(PortMapper portMapper) {
229         this.portMapper = portMapper;
230     }
231 
232     protected PortMapper getPortMapper() {
233         return portMapper;
234     }
235 
236     public void setPortResolver(PortResolver portResolver) {
237         this.portResolver = portResolver;
238     }
239 
240     protected PortResolver getPortResolver() {
241         return portResolver;
242     }
243 
244     /**
245      * Tells if we are to do a server side include of the <code>loginFormUrl</code> instead of a 302 redirect.
246      *
247      * @param serverSideRedirect
248      */
249     public void setServerSideRedirect(boolean serverSideRedirect) {
250         this.serverSideRedirect = serverSideRedirect;
251     }
252 
253     protected boolean isServerSideRedirect() {
254         return serverSideRedirect;
255     }
256 }