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 */
019package org.apache.shiro.web.filter;
020
021import org.apache.shiro.SecurityUtils;
022import org.apache.shiro.subject.Subject;
023import org.apache.shiro.web.util.WebUtils;
024
025import javax.servlet.ServletRequest;
026import javax.servlet.ServletResponse;
027import java.io.IOException;
028
029/**
030 * Superclass for any filter that controls access to a resource and may redirect the user to the login page
031 * if they are not authenticated.  This superclass provides the method
032 * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}
033 * which is used by many subclasses as the behavior when a user is unauthenticated.
034 *
035 * @since 0.9
036 */
037public abstract class AccessControlFilter extends PathMatchingFilter {
038
039    /**
040     * Simple default login URL equal to <code>/login.jsp</code>, which can be overridden by calling the
041     * {@link #setLoginUrl(String) setLoginUrl} method.
042     */
043    public static final String DEFAULT_LOGIN_URL = "/login.jsp";
044
045    /**
046     * Constant representing the HTTP 'GET' request method, equal to <code>GET</code>.
047     */
048    public static final String GET_METHOD = "GET";
049
050    /**
051     * Constant representing the HTTP 'POST' request method, equal to <code>POST</code>.
052     */
053    public static final String POST_METHOD = "POST";
054
055    /**
056     * The login url to used to authenticate a user, used when redirecting users if authentication is required.
057     */
058    private String loginUrl = DEFAULT_LOGIN_URL;
059
060    /**
061     * Returns the login URL used to authenticate a user.
062     * <p/>
063     * Most Shiro filters use this url
064     * as the location to redirect a user when the filter requires authentication.  Unless overridden, the
065     * {@link #DEFAULT_LOGIN_URL DEFAULT_LOGIN_URL} is assumed, which can be overridden via
066     * {@link #setLoginUrl(String) setLoginUrl}.
067     *
068     * @return the login URL used to authenticate a user, used when redirecting users if authentication is required.
069     */
070    public String getLoginUrl() {
071        return loginUrl;
072    }
073
074    /**
075     * Sets the login URL used to authenticate a user.
076     * <p/>
077     * Most Shiro filters use this url as the location to redirect a user when the filter requires
078     * authentication.  Unless overridden, the {@link #DEFAULT_LOGIN_URL DEFAULT_LOGIN_URL} is assumed.
079     *
080     * @param loginUrl the login URL used to authenticate a user, used when redirecting users if authentication is required.
081     */
082    public void setLoginUrl(String loginUrl) {
083        this.loginUrl = loginUrl;
084    }
085
086    /**
087     * Convenience method that acquires the Subject associated with the request.
088     * <p/>
089     * The default implementation simply returns
090     * {@link org.apache.shiro.SecurityUtils#getSubject() SecurityUtils.getSubject()}.
091     *
092     * @param request  the incoming <code>ServletRequest</code>
093     * @param response the outgoing <code>ServletResponse</code>
094     * @return the Subject associated with the request.
095     */
096    protected Subject getSubject(ServletRequest request, ServletResponse response) {
097        return SecurityUtils.getSubject();
098    }
099
100    /**
101     * Returns <code>true</code> if the request is allowed to proceed through the filter normally, or <code>false</code>
102     * if the request should be handled by the
103     * {@link #onAccessDenied(ServletRequest,ServletResponse,Object) onAccessDenied(request,response,mappedValue)}
104     * method instead.
105     *
106     * @param request     the incoming <code>ServletRequest</code>
107     * @param response    the outgoing <code>ServletResponse</code>
108     * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
109     * @return <code>true</code> if the request should proceed through the filter normally, <code>false</code> if the
110     *         request should be processed by this filter's
111     *         {@link #onAccessDenied(ServletRequest,ServletResponse,Object)} method instead.
112     * @throws Exception if an error occurs during processing.
113     */
114    protected abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;
115
116    /**
117     * Processes requests where the subject was denied access as determined by the
118     * {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed}
119     * method, retaining the {@code mappedValue} that was used during configuration.
120     * <p/>
121     * This method immediately delegates to {@link #onAccessDenied(ServletRequest,ServletResponse)} as a
122     * convenience in that most post-denial behavior does not need the mapped config again.
123     *
124     * @param request     the incoming <code>ServletRequest</code>
125     * @param response    the outgoing <code>ServletResponse</code>
126     * @param mappedValue the config specified for the filter in the matching request's filter chain.
127     * @return <code>true</code> if the request should continue to be processed; false if the subclass will
128     *         handle/render the response directly.
129     * @throws Exception if there is an error processing the request.
130     * @since 1.0
131     */
132    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
133        return onAccessDenied(request, response);
134    }
135
136    /**
137     * Processes requests where the subject was denied access as determined by the
138     * {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed}
139     * method.
140     *
141     * @param request  the incoming <code>ServletRequest</code>
142     * @param response the outgoing <code>ServletResponse</code>
143     * @return <code>true</code> if the request should continue to be processed; false if the subclass will
144     *         handle/render the response directly.
145     * @throws Exception if there is an error processing the request.
146     */
147    protected abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;
148
149    /**
150     * Returns <code>true</code> if
151     * {@link #isAccessAllowed(ServletRequest,ServletResponse,Object) isAccessAllowed(Request,Response,Object)},
152     * otherwise returns the result of
153     * {@link #onAccessDenied(ServletRequest,ServletResponse,Object) onAccessDenied(Request,Response,Object)}.
154     *
155     * @return <code>true</code> if
156     *         {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed},
157     *         otherwise returns the result of
158     *         {@link #onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse) onAccessDenied}.
159     * @throws Exception if an error occurs.
160     */
161    public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
162        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
163    }
164
165    /**
166     * Returns <code>true</code> if the incoming request is a login request, <code>false</code> otherwise.
167     * <p/>
168     * The default implementation merely returns <code>true</code> if the incoming request matches the configured
169     * {@link #getLoginUrl() loginUrl} by calling
170     * <code>{@link #pathsMatch(String, String) pathsMatch(loginUrl, request)}</code>.
171     *
172     * @param request  the incoming <code>ServletRequest</code>
173     * @param response the outgoing <code>ServletResponse</code>
174     * @return <code>true</code> if the incoming request is a login request, <code>false</code> otherwise.
175     */
176    protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
177        return pathsMatch(getLoginUrl(), request);
178    }
179
180    /**
181     * Convenience method for subclasses to use when a login redirect is required.
182     * <p/>
183     * This implementation simply calls {@link #saveRequest(javax.servlet.ServletRequest) saveRequest(request)}
184     * and then {@link #redirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse) redirectToLogin(request,response)}.
185     *
186     * @param request  the incoming <code>ServletRequest</code>
187     * @param response the outgoing <code>ServletResponse</code>
188     * @throws IOException if an error occurs.
189     */
190    protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
191        saveRequest(request);
192        redirectToLogin(request, response);
193    }
194
195    /**
196     * Convenience method merely delegates to
197     * {@link WebUtils#saveRequest(javax.servlet.ServletRequest) WebUtils.saveRequest(request)} to save the request
198     * state for reuse later.  This is mostly used to retain user request state when a redirect is issued to
199     * return the user to their originally requested url/resource.
200     * <p/>
201     * If you need to save and then immediately redirect the user to login, consider using
202     * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
203     * saveRequestAndRedirectToLogin(request,response)} directly.
204     *
205     * @param request the incoming ServletRequest to save for re-use later (for example, after a redirect).
206     */
207    protected void saveRequest(ServletRequest request) {
208        WebUtils.saveRequest(request);
209    }
210
211    /**
212     * Convenience method for subclasses that merely acquires the {@link #getLoginUrl() getLoginUrl} and redirects
213     * the request to that url.
214     * <p/>
215     * <b>N.B.</b>  If you want to issue a redirect with the intention of allowing the user to then return to their
216     * originally requested URL, don't use this method directly.  Instead you should call
217     * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
218     * saveRequestAndRedirectToLogin(request,response)}, which will save the current request state so that it can
219     * be reconstructed and re-used after a successful login.
220     *
221     * @param request  the incoming <code>ServletRequest</code>
222     * @param response the outgoing <code>ServletResponse</code>
223     * @throws IOException if an error occurs.
224     */
225    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
226        String loginUrl = getLoginUrl();
227        WebUtils.issueRedirect(request, response, loginUrl);
228    }
229
230}