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