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,
115                                               Object mappedValue) throws Exception;
116
117    /**
118     * Processes requests where the subject was denied access as determined by the
119     * {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed}
120     * method, retaining the {@code mappedValue} that was used during configuration.
121     * <p/>
122     * This method immediately delegates to {@link #onAccessDenied(ServletRequest, ServletResponse)} as a
123     * convenience in that most post-denial behavior does not need the mapped config again.
124     *
125     * @param request     the incoming <code>ServletRequest</code>
126     * @param response    the outgoing <code>ServletResponse</code>
127     * @param mappedValue the config specified for the filter in the matching request's filter chain.
128     * @return <code>true</code> if the request should continue to be processed; false if the subclass will
129     * handle/render the response directly.
130     * @throws Exception if there is an error processing the request.
131     * @since 1.0
132     */
133    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
134        return onAccessDenied(request, response);
135    }
136
137    /**
138     * Processes requests where the subject was denied access as determined by the
139     * {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed}
140     * method.
141     *
142     * @param request  the incoming <code>ServletRequest</code>
143     * @param response the outgoing <code>ServletResponse</code>
144     * @return <code>true</code> if the request should continue to be processed; false if the subclass will
145     * handle/render the response directly.
146     * @throws Exception if there is an error processing the request.
147     */
148    protected abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;
149
150    /**
151     * Returns <code>true</code> if
152     * {@link #isAccessAllowed(ServletRequest, ServletResponse, Object) isAccessAllowed(Request,Response,Object)},
153     * otherwise returns the result of
154     * {@link #onAccessDenied(ServletRequest, ServletResponse, Object) onAccessDenied(Request,Response,Object)}.
155     *
156     * @return <code>true</code> if
157     * {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed},
158     * otherwise returns the result of
159     * {@link #onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse) onAccessDenied}.
160     * @throws Exception if an error occurs.
161     */
162    public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
163        return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
164    }
165
166    /**
167     * Returns <code>true</code> if the incoming request is a login request, <code>false</code> otherwise.
168     * <p/>
169     * The default implementation merely returns <code>true</code> if the incoming request matches the configured
170     * {@link #getLoginUrl() loginUrl} by calling
171     * <code>{@link #pathsMatch(String, String) pathsMatch(loginUrl, request)}</code>.
172     *
173     * @param request  the incoming <code>ServletRequest</code>
174     * @param response the outgoing <code>ServletResponse</code>
175     * @return <code>true</code> if the incoming request is a login request, <code>false</code> otherwise.
176     */
177    protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
178        return pathsMatch(getLoginUrl(), request);
179    }
180
181
182    /**
183     * Convenience method for subclasses to use when a login redirect is required.
184     * <p/>
185     * This implementation simply calls {@link #saveRequest(javax.servlet.ServletRequest) saveRequest(request)}
186     * and then {@link #redirectToLogin(ServletRequest, ServletResponse) redirectToLogin(request,response)}.
187     *
188     * @param request  the incoming <code>ServletRequest</code>
189     * @param response the outgoing <code>ServletResponse</code>
190     * @throws IOException if an error occurs.
191     */
192    protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
193        saveRequest(request);
194        redirectToLogin(request, response);
195    }
196
197    /**
198     * Convenience method merely delegates to
199     * {@link WebUtils#saveRequest(javax.servlet.ServletRequest) WebUtils.saveRequest(request)} to save the request
200     * state for reuse later.  This is mostly used to retain user request state when a redirect is issued to
201     * return the user to their originally requested url/resource.
202     * <p/>
203     * If you need to save and then immediately redirect the user to login, consider using
204     * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
205     * saveRequestAndRedirectToLogin(request,response)} directly.
206     *
207     * @param request the incoming ServletRequest to save for re-use later (for example, after a redirect).
208     */
209    protected void saveRequest(ServletRequest request) {
210        WebUtils.saveRequest(request);
211    }
212
213    /**
214     * Convenience method for subclasses that merely acquires the {@link #getLoginUrl() getLoginUrl} and redirects
215     * the request to that url.
216     * <p/>
217     * <b>N.B.</b>  If you want to issue a redirect with the intention of allowing the user to then return to their
218     * originally requested URL, don't use this method directly.  Instead you should call
219     * {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
220     * saveRequestAndRedirectToLogin(request,response)}, which will save the current request state so that it can
221     * be reconstructed and re-used after a successful login.
222     *
223     * @param request  the incoming <code>ServletRequest</code>
224     * @param response the outgoing <code>ServletResponse</code>
225     * @throws IOException if an error occurs.
226     */
227    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
228        String loginUrl = getLoginUrl();
229        WebUtils.issueRedirect(request, response, loginUrl);
230    }
231
232}