001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.shiro.web.servlet;
020
021 import org.apache.shiro.SecurityUtils;
022 import org.apache.shiro.session.Session;
023 import org.apache.shiro.subject.ExecutionException;
024 import org.apache.shiro.subject.Subject;
025 import org.apache.shiro.web.filter.mgt.FilterChainResolver;
026 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
027 import org.apache.shiro.web.mgt.WebSecurityManager;
028 import org.apache.shiro.web.subject.WebSubject;
029 import org.slf4j.Logger;
030 import org.slf4j.LoggerFactory;
031
032 import javax.servlet.FilterChain;
033 import javax.servlet.ServletException;
034 import javax.servlet.ServletRequest;
035 import javax.servlet.ServletResponse;
036 import javax.servlet.http.HttpServletRequest;
037 import javax.servlet.http.HttpServletResponse;
038 import java.io.IOException;
039 import java.util.concurrent.Callable;
040
041 /**
042 * Abstract base class that provides all standard Shiro request filtering behavior and expects
043 * subclasses to implement configuration-specific logic (INI, XML, .properties, etc).
044 * <p/>
045 * Subclasses should perform configuration and construction logic in an overridden
046 * {@link #init()} method implementation. That implementation should make available any constructed
047 * {@code SecurityManager} and {@code FilterChainResolver} by calling
048 * {@link #setSecurityManager(org.apache.shiro.web.mgt.WebSecurityManager)} and
049 * {@link #setFilterChainResolver(org.apache.shiro.web.filter.mgt.FilterChainResolver)} methods respectively.
050 *
051 * @since 1.0
052 */
053 public abstract class AbstractShiroFilter extends OncePerRequestFilter {
054
055 private static final Logger log = LoggerFactory.getLogger(AbstractShiroFilter.class);
056
057 // Reference to the security manager used by this filter
058 private WebSecurityManager securityManager;
059
060 // Used to determine which chain should handle an incoming request/response
061 private FilterChainResolver filterChainResolver;
062
063 protected AbstractShiroFilter() {
064 }
065
066 public WebSecurityManager getSecurityManager() {
067 return securityManager;
068 }
069
070 public void setSecurityManager(WebSecurityManager sm) {
071 this.securityManager = sm;
072 }
073
074 public FilterChainResolver getFilterChainResolver() {
075 return filterChainResolver;
076 }
077
078 public void setFilterChainResolver(FilterChainResolver filterChainResolver) {
079 this.filterChainResolver = filterChainResolver;
080 }
081
082 protected final void onFilterConfigSet() throws Exception {
083 init();
084 ensureSecurityManager();
085 }
086
087 public void init() throws Exception {
088 }
089
090 /**
091 * A fallback mechanism called in {@link #onFilterConfigSet()} to ensure that the
092 * {@link #getSecurityManager() securityManager} property has been set by configuration, and if not,
093 * creates one automatically.
094 */
095 private void ensureSecurityManager() {
096 WebSecurityManager securityManager = getSecurityManager();
097 if (securityManager == null) {
098 log.info("No SecurityManager configured. Creating default.");
099 securityManager = createDefaultSecurityManager();
100 setSecurityManager(securityManager);
101 }
102 }
103
104 protected WebSecurityManager createDefaultSecurityManager() {
105 return new DefaultWebSecurityManager();
106 }
107
108 protected boolean isHttpSessions() {
109 return getSecurityManager().isHttpSessionMode();
110 }
111
112 /**
113 * Wraps the original HttpServletRequest in a {@link ShiroHttpServletRequest}, which is required for supporting
114 * Servlet Specification behavior backed by a {@link org.apache.shiro.subject.Subject Subject} instance.
115 *
116 * @param orig the original Servlet Container-provided incoming {@code HttpServletRequest} instance.
117 * @return {@link ShiroHttpServletRequest ShiroHttpServletRequest} instance wrapping the original.
118 * @since 1.0
119 */
120 protected ServletRequest wrapServletRequest(HttpServletRequest orig) {
121 return new ShiroHttpServletRequest(orig, getServletContext(), isHttpSessions());
122 }
123
124 /**
125 * Prepares the {@code ServletRequest} instance that will be passed to the {@code FilterChain} for request
126 * processing.
127 * <p/>
128 * If the {@code ServletRequest} is an instance of {@link HttpServletRequest}, the value returned from this method
129 * is obtained by calling {@link #wrapServletRequest(javax.servlet.http.HttpServletRequest)} to allow Shiro-specific
130 * HTTP behavior, otherwise the original {@code ServletRequest} argument is returned.
131 *
132 * @param request the incoming ServletRequest
133 * @param response the outgoing ServletResponse
134 * @param chain the Servlet Container provided {@code FilterChain} that will receive the returned request.
135 * @return the {@code ServletRequest} instance that will be passed to the {@code FilterChain} for request processing.
136 * @since 1.0
137 */
138 @SuppressWarnings({"UnusedDeclaration"})
139 protected ServletRequest prepareServletRequest(ServletRequest request, ServletResponse response, FilterChain chain) {
140 ServletRequest toUse = request;
141 if (request instanceof HttpServletRequest) {
142 HttpServletRequest http = (HttpServletRequest) request;
143 toUse = wrapServletRequest(http);
144 }
145 return toUse;
146 }
147
148 /**
149 * Returns a new {@link ShiroHttpServletResponse} instance, wrapping the {@code orig} argument, in order to provide
150 * correct URL rewriting behavior required by the Servlet Specification when using Shiro-based sessions (and not
151 * Servlet Container HTTP-based sessions).
152 *
153 * @param orig the original {@code HttpServletResponse} instance provided by the Servlet Container.
154 * @param request the {@code ShiroHttpServletRequest} instance wrapping the original request.
155 * @return the wrapped ServletResponse instance to use during {@link FilterChain} execution.
156 * @since 1.0
157 */
158 protected ServletResponse wrapServletResponse(HttpServletResponse orig, ShiroHttpServletRequest request) {
159 return new ShiroHttpServletResponse(orig, getServletContext(), request);
160 }
161
162 /**
163 * Prepares the {@code ServletResponse} instance that will be passed to the {@code FilterChain} for request
164 * processing.
165 * <p/>
166 * This implementation delegates to {@link #wrapServletRequest(javax.servlet.http.HttpServletRequest)}
167 * only if Shiro-based sessions are enabled (that is, !{@link #isHttpSessions()}) and the request instance is a
168 * {@link ShiroHttpServletRequest}. This ensures that any URL rewriting that occurs is handled correctly using the
169 * Shiro-managed Session's sessionId and not a servlet container session ID.
170 * <p/>
171 * If HTTP-based sessions are enabled (the default), then this method does nothing and just returns the
172 * {@code ServletResponse} argument as-is, relying on the default Servlet Container URL rewriting logic.
173 *
174 * @param request the incoming ServletRequest
175 * @param response the outgoing ServletResponse
176 * @param chain the Servlet Container provided {@code FilterChain} that will receive the returned request.
177 * @return the {@code ServletResponse} instance that will be passed to the {@code FilterChain} during request processing.
178 * @since 1.0
179 */
180 @SuppressWarnings({"UnusedDeclaration"})
181 protected ServletResponse prepareServletResponse(ServletRequest request, ServletResponse response, FilterChain chain) {
182 ServletResponse toUse = response;
183 if (!isHttpSessions() && (request instanceof ShiroHttpServletRequest) &&
184 (response instanceof HttpServletResponse)) {
185 //the ShiroHttpServletResponse exists to support URL rewriting for session ids. This is only needed if
186 //using Shiro sessions (i.e. not simple HttpSession based sessions):
187 toUse = wrapServletResponse((HttpServletResponse) response, (ShiroHttpServletRequest) request);
188 }
189 return toUse;
190 }
191
192 /**
193 * Creates a {@link WebSubject} instance to associate with the incoming request/response pair which will be used
194 * throughout the request/response execution.
195 *
196 * @param request the incoming {@code ServletRequest}
197 * @param response the outgoing {@code ServletResponse}
198 * @return the {@code WebSubject} instance to associate with the request/response execution
199 * @since 1.0
200 */
201 protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
202 return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
203 }
204
205 /**
206 * Updates any 'native' Session's last access time that might exist to the timestamp when this method is called.
207 * If native sessions are not enabled (that is, standard Servlet container sessions are being used) or there is no
208 * session ({@code subject.getSession(false) == null}), this method does nothing.
209 * <p/>This method implementation merely calls
210 * <code>Session.{@link org.apache.shiro.session.Session#touch() touch}()</code> on the session.
211 *
212 * @param request incoming request - ignored, but available to subclasses that might wish to override this method
213 * @param response outgoing response - ignored, but available to subclasses that might wish to override this method
214 * @since 1.0
215 */
216 @SuppressWarnings({"UnusedDeclaration"})
217 protected void updateSessionLastAccessTime(ServletRequest request, ServletResponse response) {
218 if (!isHttpSessions()) { //'native' sessions
219 Subject subject = SecurityUtils.getSubject();
220 //Subject should never _ever_ be null, but just in case:
221 if (subject != null) {
222 Session session = subject.getSession(false);
223 if (session != null) {
224 try {
225 session.touch();
226 } catch (Throwable t) {
227 log.error("session.touch() method invocation has failed. Unable to update" +
228 "the corresponding session's last access time based on the incoming request.", t);
229 }
230 }
231 }
232 }
233 }
234
235 /**
236 * {@code doFilterInternal} implementation that sets-up, executes, and cleans-up a Shiro-filtered request. It
237 * performs the following ordered operations:
238 * <ol>
239 * <li>{@link #prepareServletRequest(ServletRequest, ServletResponse, FilterChain) Prepares}
240 * the incoming {@code ServletRequest} for use during Shiro's processing</li>
241 * <li>{@link #prepareServletResponse(ServletRequest, ServletResponse, FilterChain) Prepares}
242 * the outgoing {@code ServletResponse} for use during Shiro's processing</li>
243 * <li> {@link #createSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) Creates} a
244 * {@link Subject} instance based on the specified request/response pair.</li>
245 * <li>Finally {@link Subject#execute(Runnable) executes} the
246 * {@link #updateSessionLastAccessTime(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} and
247 * {@link #executeChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}
248 * methods</li>
249 * </ol>
250 * <p/>
251 * The {@code Subject.}{@link Subject#execute(Runnable) execute(Runnable)} call in step #4 is used as an
252 * implementation technique to guarantee proper thread binding and restoration is completed successfully.
253 *
254 * @param servletRequest the incoming {@code ServletRequest}
255 * @param servletResponse the outgoing {@code ServletResponse}
256 * @param chain the container-provided {@code FilterChain} to execute
257 * @throws IOException if an IO error occurs
258 * @throws javax.servlet.ServletException if an Throwable other than an IOException
259 */
260 protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
261 throws ServletException, IOException {
262
263 Throwable t = null;
264
265 try {
266 final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
267 final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
268
269 final Subject subject = createSubject(request, response);
270
271 //noinspection unchecked
272 subject.execute(new Callable() {
273 public Object call() throws Exception {
274 updateSessionLastAccessTime(request, response);
275 executeChain(request, response, chain);
276 return null;
277 }
278 });
279 } catch (ExecutionException ex) {
280 t = ex.getCause();
281 } catch (Throwable throwable) {
282 t = throwable;
283 }
284
285 if (t != null) {
286 if (t instanceof ServletException) {
287 throw (ServletException) t;
288 }
289 if (t instanceof IOException) {
290 throw (IOException) t;
291 }
292 //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
293 String msg = "Filtered request failed.";
294 throw new ServletException(msg, t);
295 }
296 }
297
298 /**
299 * Returns the {@code FilterChain} to execute for the given request.
300 * <p/>
301 * The {@code origChain} argument is the
302 * original {@code FilterChain} supplied by the Servlet Container, but it may be modified to provide
303 * more behavior by pre-pending further chains according to the Shiro configuration.
304 * <p/>
305 * This implementation returns the chain that will actually be executed by acquiring the chain from a
306 * {@link #getFilterChainResolver() filterChainResolver}. The resolver determines exactly which chain to
307 * execute, typically based on URL configuration. If no chain is returned from the resolver call
308 * (returns {@code null}), then the {@code origChain} will be returned by default.
309 *
310 * @param request the incoming ServletRequest
311 * @param response the outgoing ServletResponse
312 * @param origChain the original {@code FilterChain} provided by the Servlet Container
313 * @return the {@link FilterChain} to execute for the given request
314 * @since 1.0
315 */
316 protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
317 FilterChain chain = origChain;
318
319 FilterChainResolver resolver = getFilterChainResolver();
320 if (resolver == null) {
321 log.debug("No FilterChainResolver configured. Returning original FilterChain.");
322 return origChain;
323 }
324
325 FilterChain resolved = resolver.getChain(request, response, origChain);
326 if (resolved != null) {
327 log.trace("Resolved a configured FilterChain for the current request.");
328 chain = resolved;
329 } else {
330 log.trace("No FilterChain configured for the current request. Using the default.");
331 }
332
333 return chain;
334 }
335
336 /**
337 * Executes a {@link FilterChain} for the given request.
338 * <p/>
339 * This implementation first delegates to
340 * <code>{@link #getExecutionChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) getExecutionChain}</code>
341 * to allow the application's Shiro configuration to determine exactly how the chain should execute. The resulting
342 * value from that call is then executed directly by calling the returned {@code FilterChain}'s
343 * {@link FilterChain#doFilter doFilter} method. That is:
344 * <pre>
345 * FilterChain chain = {@link #getExecutionChain}(request, response, origChain);
346 * chain.{@link FilterChain#doFilter doFilter}(request,response);</pre>
347 *
348 * @param request the incoming ServletRequest
349 * @param response the outgoing ServletResponse
350 * @param origChain the Servlet Container-provided chain that may be wrapped further by an application-configured
351 * chain of Filters.
352 * @throws IOException if the underlying {@code chain.doFilter} call results in an IOException
353 * @throws ServletException if the underlying {@code chain.doFilter} call results in a ServletException
354 * @since 1.0
355 */
356 protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
357 throws IOException, ServletException {
358 FilterChain chain = getExecutionChain(request, response, origChain);
359 chain.doFilter(request, response);
360 }
361 }