1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.acegisecurity.context;
17
18 import org.apache.commons.logging.Log;
19 import org.apache.commons.logging.LogFactory;
20
21 import org.springframework.beans.factory.InitializingBean;
22
23 import java.io.IOException;
24
25 import javax.servlet.Filter;
26 import javax.servlet.FilterChain;
27 import javax.servlet.FilterConfig;
28 import javax.servlet.ServletException;
29 import javax.servlet.ServletRequest;
30 import javax.servlet.ServletResponse;
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpSession;
33
34
35 /***
36 * <p>
37 * Populates the {@link SecurityContextHolder}</code> with information obtained
38 * from the <code>HttpSession</code>.
39 * </p>
40 *
41 * <p>
42 * The <code>HttpSession</code> will be queried to retrieve the
43 * <code>SecurityContext</code> that should be stored against the
44 * <code>SecurityContextHolder</code> for the duration of the web request. At
45 * the end of the web request, any updates made to the
46 * <code>SecurityContextHolder</code> will be persisted back to the
47 * <code>HttpSession</code> by this filter.
48 * </p>
49 *
50 * <p>
51 * If a valid <code>SecurityContext</code> cannot be obtained from the
52 * <code>HttpSession</code> for whatever reason, a fresh
53 * <code>SecurityContext</code> will be created and used instead. The created
54 * object will be of the instance defined by the {@link #setContext(Class)}
55 * method (which defaults to {@link
56 * org.acegisecurity.context.SecurityContextImpl}.
57 * </p>
58 *
59 * <p>
60 * No <code>HttpSession</code> will be created by this filter if one does not
61 * already exist. If at the end of the web request the
62 * <code>HttpSession</code> does not exist, a <code>HttpSession</code> will
63 * <b>only</b> be created if the current contents of the
64 * <code>SecurityContextHolder</code> are not {@link
65 * java.lang.Object#equals(java.lang.Object)} to a <code>new</code> instance
66 * of {@link #setContext(Class)}. This avoids needless
67 * <code>HttpSession</code> creation, but automates the storage of changes
68 * made to the <code>SecurityContextHolder</code>.
69 * </p>
70 *
71 * <p>
72 * This filter will only execute once per request, to resolve servlet container
73 * (specifically Weblogic) incompatibilities.
74 * </p>
75 *
76 * <p>
77 * If for whatever reason no <code>HttpSession</code> should <b>ever</b> be
78 * created (eg this filter is only being used with Basic authentication or
79 * similar clients that will never present the same <code>jsessionid</code>
80 * etc), the {@link #setAllowSessionCreation(boolean)} should be set to
81 * <code>false</code>. Only do this if you really need to conserve server
82 * memory and ensure all classes using the <code>SecurityContextHolder</code> are
83 * designed to have no persistence of the <code>SecurityContext</code> between web
84 * requests.
85 * </p>
86 *
87 * <p>
88 * This filter MUST be executed BEFORE any authentication procesing mechanisms.
89 * Authentication processing mechanisms (eg BASIC, CAS processing filters etc)
90 * expect the <code>SecurityContextHolder</code> to contain a valid
91 * <code>SecurityContext</code> by the time they execute.
92 * </p>
93 *
94 * @author Ben Alex
95 * @author Patrick Burleson
96 * @version $Id: HttpSessionContextIntegrationFilter.java,v 1.12 2005/11/17 00:55:49 benalex Exp $
97 */
98 public class HttpSessionContextIntegrationFilter implements InitializingBean,
99 Filter {
100
101
102 protected static final Log logger = LogFactory.getLog(HttpSessionContextIntegrationFilter.class);
103 private static final String FILTER_APPLIED = "__acegi_session_integration_filter_applied";
104 public static final String ACEGI_SECURITY_CONTEXT_KEY = "ACEGI_SECURITY_CONTEXT";
105
106
107
108 private Class context = SecurityContextImpl.class;
109 private Object contextObject;
110
111 /***
112 * Indicates if this filter can create a <code>HttpSession</code> if needed
113 * (sessions are always created sparingly, but setting this value to false
114 * will prohibit sessions from ever being created). Defaults to true.
115 */
116 private boolean allowSessionCreation = true;
117
118
119
120 public void setAllowSessionCreation(boolean allowSessionCreation) {
121 this.allowSessionCreation = allowSessionCreation;
122 }
123
124 public boolean isAllowSessionCreation() {
125 return allowSessionCreation;
126 }
127
128 public void setContext(Class secureContext) {
129 this.context = secureContext;
130 }
131
132 public Class getContext() {
133 return context;
134 }
135
136 public void afterPropertiesSet() throws Exception {
137 if ((this.context == null)
138 || (!SecurityContext.class.isAssignableFrom(this.context))) {
139 throw new IllegalArgumentException(
140 "context must be defined and implement SecurityContext (typically use org.acegisecurity.context.SecurityContextImpl; existing class is "
141 + this.context + ")");
142 }
143
144 this.contextObject = generateNewContext();
145 }
146
147 /***
148 * Does nothing. We use IoC container lifecycle services instead.
149 */
150 public void destroy() {}
151
152 public void doFilter(ServletRequest request, ServletResponse response,
153 FilterChain chain) throws IOException, ServletException {
154 if ((request != null) && (request.getAttribute(FILTER_APPLIED) != null)) {
155
156 chain.doFilter(request, response);
157 } else {
158 if (request != null) {
159 request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
160 }
161
162 HttpSession httpSession = null;
163 boolean httpSessionExistedAtStartOfRequest = false;
164
165 try {
166 httpSession = ((HttpServletRequest) request).getSession(false);
167 } catch (IllegalStateException ignored) {}
168
169 if (httpSession != null) {
170 httpSessionExistedAtStartOfRequest = true;
171
172 Object contextFromSessionObject = httpSession.getAttribute(ACEGI_SECURITY_CONTEXT_KEY);
173
174 if (contextFromSessionObject != null) {
175 if (contextFromSessionObject instanceof SecurityContext) {
176 if (logger.isDebugEnabled()) {
177 logger.debug(
178 "Obtained from ACEGI_SECURITY_CONTEXT a valid SecurityContext and set to SecurityContextHolder: '"
179 + contextFromSessionObject + "'");
180 }
181
182 SecurityContextHolder.setContext((SecurityContext) contextFromSessionObject);
183 } else {
184 if (logger.isWarnEnabled()) {
185 logger.warn(
186 "ACEGI_SECURITY_CONTEXT did not contain a SecurityContext but contained: '"
187 + contextFromSessionObject
188 + "'; are you improperly modifying the HttpSession directly (you should always use SecurityContextHolder) or using the HttpSession attribute reserved for this class? - new SecurityContext instance associated with SecurityContextHolder");
189 }
190
191 SecurityContextHolder.setContext(generateNewContext());
192 }
193 } else {
194 if (logger.isDebugEnabled()) {
195 logger.debug(
196 "HttpSession returned null object for ACEGI_SECURITY_CONTEXT - new SecurityContext instance associated with SecurityContextHolder");
197 }
198
199 SecurityContextHolder.setContext(generateNewContext());
200 }
201 } else {
202 if (logger.isDebugEnabled()) {
203 logger.debug(
204 "No HttpSession currently exists - new SecurityContext instance associated with SecurityContextHolder");
205 }
206
207 SecurityContextHolder.setContext(generateNewContext());
208 }
209
210
211
212
213 httpSession = null;
214
215
216 int contextWhenChainProceeded = SecurityContextHolder.getContext()
217 .hashCode();
218
219 try {
220 chain.doFilter(request, response);
221 } catch (IOException ioe) {
222 throw ioe;
223 } catch (ServletException se) {
224 throw se;
225 } finally {
226
227
228 try {
229 httpSession = ((HttpServletRequest) request).getSession(false);
230 } catch (IllegalStateException ignored) {}
231
232 if ((httpSession == null) && httpSessionExistedAtStartOfRequest) {
233 if (logger.isDebugEnabled()) {
234 logger.debug(
235 "HttpSession is now null, but was not null at start of request; session was invalidated, so do not create a new session");
236 }
237 }
238
239
240 if ((httpSession == null)
241 && !httpSessionExistedAtStartOfRequest) {
242 if (!allowSessionCreation) {
243 if (logger.isDebugEnabled()) {
244 logger.debug(
245 "The HttpSession is currently null, and the HttpSessionContextIntegrationFilter is prohibited from creating a HttpSession (because the allowSessionCreation property is false) - SecurityContext thus not stored for next request");
246 }
247 } else if (!contextObject.equals(
248 SecurityContextHolder.getContext())) {
249 if (logger.isDebugEnabled()) {
250 logger.debug(
251 "HttpSession being created as SecurityContextHolder contents are non-default");
252 }
253
254 try {
255 httpSession = ((HttpServletRequest) request)
256 .getSession(true);
257 } catch (IllegalStateException ignored) {}
258 } else {
259 if (logger.isDebugEnabled()) {
260 logger.debug(
261 "HttpSession is null, but SecurityContextHolder has not changed from default: ' "
262 + SecurityContextHolder.getContext()
263 + "'; not creating HttpSession or storing SecurityContextHolder contents");
264 }
265 }
266 }
267
268
269
270 if ((httpSession != null)
271 && (SecurityContextHolder.getContext().hashCode() != contextWhenChainProceeded)) {
272 httpSession.setAttribute(ACEGI_SECURITY_CONTEXT_KEY,
273 SecurityContextHolder.getContext());
274
275 if (logger.isDebugEnabled()) {
276 logger.debug("SecurityContext stored to HttpSession: '"
277 + SecurityContextHolder.getContext() + "'");
278 }
279 }
280
281
282 SecurityContextHolder.setContext(generateNewContext());
283
284 if (logger.isDebugEnabled()) {
285 logger.debug(
286 "SecurityContextHolder set to new context, as request processing completed");
287 }
288 }
289 }
290 }
291
292 public SecurityContext generateNewContext() throws ServletException {
293 try {
294 return (SecurityContext) this.context.newInstance();
295 } catch (InstantiationException ie) {
296 throw new ServletException(ie);
297 } catch (IllegalAccessException iae) {
298 throw new ServletException(iae);
299 }
300 }
301
302 /***
303 * Does nothing. We use IoC container lifecycle services instead.
304 *
305 * @param filterConfig ignored
306 *
307 * @throws ServletException ignored
308 */
309 public void init(FilterConfig filterConfig) throws ServletException {}
310 }