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.acegisecurity.ui.webapp;
17  
18  import org.acegisecurity.AuthenticationException;
19  
20  import org.acegisecurity.ui.AuthenticationEntryPoint;
21  
22  import org.acegisecurity.util.PortMapper;
23  import org.acegisecurity.util.PortMapperImpl;
24  import org.acegisecurity.util.PortResolver;
25  import org.acegisecurity.util.PortResolverImpl;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  
30  import org.springframework.beans.factory.InitializingBean;
31  
32  import org.springframework.util.Assert;
33  
34  import java.io.IOException;
35  
36  import javax.servlet.RequestDispatcher;
37  import javax.servlet.ServletException;
38  import javax.servlet.ServletRequest;
39  import javax.servlet.ServletResponse;
40  import javax.servlet.http.HttpServletRequest;
41  import javax.servlet.http.HttpServletResponse;
42  
43  /**
44   * <p>
45   * Used by the <code>SecurityEnforcementFilter</code> to commence
46   * authentication via the {@link AuthenticationProcessingFilter}. This object
47   * holds the location of the login form, relative to the web app context path,
48   * and is used to commence a redirect to that form.
49   * </p>
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   * </p>
59   * 
60   * @author Ben Alex
61   * @author colin sampaleanu
62   * @author Omri Spector
63   * @version $Id: AuthenticationProcessingFilterEntryPoint.java 1873 2007-05-25
64   * 03:21:17Z benalex $
65   */
66  public class AuthenticationProcessingFilterEntryPoint implements AuthenticationEntryPoint, InitializingBean {
67  	// ~ Static fields/initializers
68  	// =====================================================================================
69  
70  	private static final Log logger = LogFactory.getLog(AuthenticationProcessingFilterEntryPoint.class);
71  
72  	// ~ Instance fields
73  	// ================================================================================================
74  
75  	private PortMapper portMapper = new PortMapperImpl();
76  
77  	private PortResolver portResolver = new PortResolverImpl();
78  
79  	private String loginFormUrl;
80  
81  	private boolean forceHttps = false;
82  
83  	private boolean serverSideRedirect = false;
84  
85  	// ~ Methods
86  	// ========================================================================================================
87  
88  	public void afterPropertiesSet() throws Exception {
89  		Assert.hasLength(loginFormUrl, "loginFormUrl must be specified");
90  		Assert.notNull(portMapper, "portMapper must be specified");
91  		Assert.notNull(portResolver, "portResolver must be specified");
92  	}
93  
94  	/**
95  	 * Allows subclasses to modify the login form URL that should be applicable
96  	 * for a given request.
97  	 * 
98  	 * @param request the request
99  	 * @param response the response
100 	 * @param exception the exception
101 	 * @return the URL (cannot be null or empty; defaults to
102 	 * {@link #getLoginFormUrl()})
103 	 */
104 	protected String determineUrlToUseForThisRequest(HttpServletRequest request, HttpServletResponse response,
105 			AuthenticationException exception) {
106 		return getLoginFormUrl();
107 	}
108 
109 	public void commence(ServletRequest request, ServletResponse response, AuthenticationException authException)
110 			throws IOException, ServletException {
111 		HttpServletRequest req = (HttpServletRequest) request;
112 		HttpServletResponse resp = (HttpServletResponse) response;
113 		String scheme = request.getScheme();
114 		String serverName = request.getServerName();
115 		int serverPort = portResolver.getServerPort(request);
116 		String contextPath = req.getContextPath();
117 
118 		boolean inHttp = "http".equals(scheme.toLowerCase());
119 		boolean inHttps = "https".equals(scheme.toLowerCase());
120 
121 		boolean includePort = true;
122 
123 		String redirectUrl = null;
124 		boolean doForceHttps = false;
125 		Integer httpsPort = null;
126 
127 		if (inHttp && (serverPort == 80)) {
128 			includePort = false;
129 		}
130 		else if (inHttps && (serverPort == 443)) {
131 			includePort = false;
132 		}
133 
134 		if (forceHttps && inHttp) {
135 			httpsPort = (Integer) portMapper.lookupHttpsPort(new Integer(serverPort));
136 
137 			if (httpsPort != null) {
138 				doForceHttps = true;
139 				if (httpsPort.intValue() == 443) {
140 					includePort = false;
141 				}
142 				else {
143 					includePort = true;
144 				}
145 			}
146 
147 		}
148 
149 		String loginForm = determineUrlToUseForThisRequest(req, resp, authException);
150 
151 		if (serverSideRedirect) {
152 
153 			if (doForceHttps) {
154 
155 				// before doing server side redirect, we need to do client
156 				// redirect to https.
157 
158 				String servletPath = req.getServletPath();
159 				String pathInfo = req.getPathInfo();
160 				String query = req.getQueryString();
161 
162 				redirectUrl = "https://" + serverName + ((includePort) ? (":" + httpsPort) : "") + contextPath
163 						+ servletPath + (pathInfo == null ? "" : pathInfo) + (query == null ? "" : "?" + query);
164 
165 			}
166 			else {
167 
168 				if (logger.isDebugEnabled()) {
169 					logger.debug("Server side forward to: " + loginForm);
170 				}
171 
172 				RequestDispatcher dispatcher = req.getRequestDispatcher(loginForm);
173 
174 				dispatcher.forward(request, response);
175 
176 				return;
177 
178 			}
179 
180 		}
181 		else {
182 
183 			if (doForceHttps) {
184 
185 				redirectUrl = "https://" + serverName + ((includePort) ? (":" + httpsPort) : "") + contextPath
186 						+ loginForm;
187 
188 			}
189 			else {
190 
191 				redirectUrl = scheme + "://" + serverName + ((includePort) ? (":" + serverPort) : "") + contextPath
192 						+ loginForm;
193 
194 			}
195 		}
196 
197 		if (logger.isDebugEnabled()) {
198 			logger.debug("Redirecting to: " + redirectUrl);
199 		}
200 
201 		((HttpServletResponse) response).sendRedirect(((HttpServletResponse) response).encodeRedirectURL(redirectUrl));
202 	}
203 
204 	public boolean getForceHttps() {
205 		return forceHttps;
206 	}
207 
208 	public String getLoginFormUrl() {
209 		return loginFormUrl;
210 	}
211 
212 	public PortMapper getPortMapper() {
213 		return portMapper;
214 	}
215 
216 	public PortResolver getPortResolver() {
217 		return portResolver;
218 	}
219 
220 	public boolean isServerSideRedirect() {
221 		return serverSideRedirect;
222 	}
223 
224 	/**
225 	 * Set to true to force login form access to be via https. If this value is
226 	 * ture (the default is false), and the incoming request for the protected
227 	 * resource which triggered the interceptor was not already
228 	 * <code>https</code>, then
229 	 * 
230 	 * @param forceHttps
231 	 */
232 	public void setForceHttps(boolean forceHttps) {
233 		this.forceHttps = forceHttps;
234 	}
235 
236 	/**
237 	 * The URL where the <code>AuthenticationProcessingFilter</code> login
238 	 * page can be found. Should be relative to the web-app context path, and
239 	 * include a leading <code>/</code>
240 	 * 
241 	 * @param loginFormUrl
242 	 */
243 	public void setLoginFormUrl(String loginFormUrl) {
244 		this.loginFormUrl = loginFormUrl;
245 	}
246 
247 	public void setPortMapper(PortMapper portMapper) {
248 		this.portMapper = portMapper;
249 	}
250 
251 	public void setPortResolver(PortResolver portResolver) {
252 		this.portResolver = portResolver;
253 	}
254 
255 	/**
256 	 * Tells if we are to do a server side include of the
257 	 * <code>loginFormUrl</code> instead of a 302 redirect.
258 	 * 
259 	 * @param serverSideRedirect
260 	 */
261 	public void setServerSideRedirect(boolean serverSideRedirect) {
262 		this.serverSideRedirect = serverSideRedirect;
263 	}
264 
265 }