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.filter;
020    
021    import org.apache.shiro.SecurityUtils;
022    import org.apache.shiro.subject.Subject;
023    import org.apache.shiro.web.util.WebUtils;
024    
025    import javax.servlet.ServletRequest;
026    import javax.servlet.ServletResponse;
027    import 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     */
037    public 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    }