Source code: net/sf/acegisecurity/intercept/web/SecurityEnforcementFilter.java
1 /* Copyright 2004, 2005 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 net.sf.acegisecurity.intercept.web;
17
18 import net.sf.acegisecurity.AccessDeniedException;
19 import net.sf.acegisecurity.AuthenticationException;
20 import net.sf.acegisecurity.AuthenticationTrustResolver;
21 import net.sf.acegisecurity.AuthenticationTrustResolverImpl;
22 import net.sf.acegisecurity.InsufficientAuthenticationException;
23 import net.sf.acegisecurity.context.security.SecureContextUtils;
24 import net.sf.acegisecurity.ui.AbstractProcessingFilter;
25 import net.sf.acegisecurity.util.PortResolver;
26 import net.sf.acegisecurity.util.PortResolverImpl;
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.Filter;
38 import javax.servlet.FilterChain;
39 import javax.servlet.FilterConfig;
40 import javax.servlet.ServletException;
41 import javax.servlet.ServletRequest;
42 import javax.servlet.ServletResponse;
43 import javax.servlet.http.HttpServletRequest;
44 import javax.servlet.http.HttpServletResponse;
45
46
47 /**
48 * Wraps requests to the {@link FilterSecurityInterceptor}.
49 *
50 * <p>
51 * This filter is necessary because it provides the bridge between incoming
52 * requests and the <code>FilterSecurityInterceptor</code> instance.
53 * </p>
54 *
55 * <p>
56 * If an {@link AuthenticationException} is detected, the filter will launch the
57 * <code>authenticationEntryPoint</code>. This allows common handling of
58 * authentication failures originating from any subclass of {@link
59 * net.sf.acegisecurity.intercept.AbstractSecurityInterceptor}.
60 * </p>
61 *
62 * <p>
63 * If an {@link AccessDeniedException} is detected, the filter will determine
64 * whether or not the user is an anonymous user. If they are an anonymous
65 * user, the <code>authenticationEntryPoint</code> will be launched. If they
66 * are not an anonymous user, the filter will respond with a
67 * <code>HttpServletResponse.SC_FORBIDDEN</code> (403 error). In addition,
68 * the <code>AccessDeniedException</code> itself will be placed in the
69 * <code>HttpSession</code> attribute keyed against {@link
70 * #ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY} (to allow access to the stack
71 * trace etc). Again, this allows common access denied handling irrespective
72 * of the originating security interceptor.
73 * </p>
74 *
75 * <p>
76 * To use this filter, it is necessary to specify the following properties:
77 * </p>
78 *
79 * <ul>
80 * <li>
81 * <code>filterSecurityInterceptor</code> indicates the
82 * <code>FilterSecurityInterceptor</code> to delegate HTTP security decisions
83 * to.
84 * </li>
85 * <li>
86 * <code>authenticationEntryPoint</code> indicates the handler that should
87 * commence the authentication process if an
88 * <code>AuthenticationException</code> is detected. Note that this may also
89 * switch the current protocol from http to https for an SSL login.
90 * </li>
91 * <li>
92 * <code>portResolver</code> is used to determine the "real" port that a
93 * request was received on.
94 * </li>
95 * </ul>
96 *
97 * <P>
98 * <B>Do not use this class directly.</B> Instead configure
99 * <code>web.xml</code> to use the {@link
100 * net.sf.acegisecurity.util.FilterToBeanProxy}.
101 * </p>
102 *
103 * @author Ben Alex
104 * @author colin sampaleanu
105 * @version $Id: SecurityEnforcementFilter.java,v 1.16 2005/03/16 16:57:28 luke_t Exp $
106 */
107 public class SecurityEnforcementFilter implements Filter, InitializingBean {
108 //~ Static fields/initializers =============================================
109
110 private static final Log logger = LogFactory.getLog(SecurityEnforcementFilter.class);
111 public static final String ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY = "ACEGI_SECURITY_403_EXCEPTION";
112
113 //~ Instance fields ========================================================
114
115 private AuthenticationEntryPoint authenticationEntryPoint;
116 private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
117 private FilterSecurityInterceptor filterSecurityInterceptor;
118 private PortResolver portResolver = new PortResolverImpl();
119
120 //~ Methods ================================================================
121
122 public void setAuthenticationEntryPoint(
123 AuthenticationEntryPoint authenticationEntryPoint) {
124 this.authenticationEntryPoint = authenticationEntryPoint;
125 }
126
127 public AuthenticationEntryPoint getAuthenticationEntryPoint() {
128 return authenticationEntryPoint;
129 }
130
131 public void setAuthenticationTrustResolver(
132 AuthenticationTrustResolver authenticationTrustResolver) {
133 this.authenticationTrustResolver = authenticationTrustResolver;
134 }
135
136 public AuthenticationTrustResolver getAuthenticationTrustResolver() {
137 return authenticationTrustResolver;
138 }
139
140 public void setFilterSecurityInterceptor(
141 FilterSecurityInterceptor filterSecurityInterceptor) {
142 this.filterSecurityInterceptor = filterSecurityInterceptor;
143 }
144
145 public FilterSecurityInterceptor getFilterSecurityInterceptor() {
146 return filterSecurityInterceptor;
147 }
148
149 public void setPortResolver(PortResolver portResolver) {
150 this.portResolver = portResolver;
151 }
152
153 public PortResolver getPortResolver() {
154 return portResolver;
155 }
156
157 public void afterPropertiesSet() throws Exception {
158 Assert.notNull(authenticationEntryPoint,
159 "authenticationEntryPoint must be specified");
160 Assert.notNull(filterSecurityInterceptor,
161 "filterSecurityInterceptor must be specified");
162 Assert.notNull(portResolver, "portResolver must be specified");
163 Assert.notNull(authenticationTrustResolver,
164 "authenticationTrustResolver must be specified");
165 }
166
167 public void destroy() {}
168
169 public void doFilter(ServletRequest request, ServletResponse response,
170 FilterChain chain) throws IOException, ServletException {
171 if (!(request instanceof HttpServletRequest)) {
172 throw new ServletException("HttpServletRequest required");
173 }
174
175 if (!(response instanceof HttpServletResponse)) {
176 throw new ServletException("HttpServletResponse required");
177 }
178
179 FilterInvocation fi = new FilterInvocation(request, response, chain);
180
181 try {
182 filterSecurityInterceptor.invoke(fi);
183
184 if (logger.isDebugEnabled()) {
185 logger.debug("Chain processed normally");
186 }
187 } catch (AuthenticationException authentication) {
188 if (logger.isDebugEnabled()) {
189 logger.debug("Authentication exception occurred; redirecting to authentication entry point",
190 authentication);
191 }
192
193 sendStartAuthentication(fi, authentication);
194 } catch (AccessDeniedException accessDenied) {
195 if (authenticationTrustResolver.isAnonymous(
196 SecureContextUtils.getSecureContext().getAuthentication())) {
197 if (logger.isDebugEnabled()) {
198 logger.debug("Access is denied (user is anonymous); redirecting to authentication entry point",
199 accessDenied);
200 }
201
202 sendStartAuthentication(fi,
203 new InsufficientAuthenticationException(
204 "Full authentication is required to access this resource"));
205 } else {
206 if (logger.isDebugEnabled()) {
207 logger.debug("Access is denied (user is not anonymous); sending back forbidden response",
208 accessDenied);
209 }
210
211 sendAccessDeniedError(fi, accessDenied);
212 }
213 } catch (Throwable otherException) {
214 throw new ServletException(otherException);
215 }
216 }
217
218 public void init(FilterConfig filterConfig) throws ServletException {}
219
220 protected void sendAccessDeniedError(FilterInvocation fi,
221 AccessDeniedException accessDenied)
222 throws ServletException, IOException {
223 ((HttpServletRequest) fi.getRequest()).getSession().setAttribute(ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY,
224 accessDenied);
225 ((HttpServletResponse) fi.getResponse()).sendError(HttpServletResponse.SC_FORBIDDEN,
226 accessDenied.getMessage()); // 403
227 }
228
229 protected void sendStartAuthentication(FilterInvocation fi,
230 AuthenticationException reason) throws ServletException, IOException {
231 HttpServletRequest request = (HttpServletRequest) fi.getRequest();
232
233 int port = portResolver.getServerPort(request);
234 boolean includePort = true;
235
236 if ("http".equals(request.getScheme().toLowerCase()) && (port == 80)) {
237 includePort = false;
238 }
239
240 if ("https".equals(request.getScheme().toLowerCase()) && (port == 443)) {
241 includePort = false;
242 }
243
244 String targetUrl = request.getScheme() + "://"
245 + request.getServerName() + ((includePort) ? (":" + port) : "")
246 + request.getContextPath() + fi.getRequestUrl();
247
248 if (logger.isDebugEnabled()) {
249 logger.debug(
250 "Authentication entry point being called; target URL added to Session: "
251 + targetUrl);
252 }
253
254 ((HttpServletRequest) request).getSession().setAttribute(AbstractProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY,
255 targetUrl);
256 authenticationEntryPoint.commence(request,
257 (HttpServletResponse) fi.getResponse(), reason);
258 }
259 }