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.cas;
020    
021    import org.apache.shiro.authc.AuthenticationException;
022    import org.apache.shiro.authc.AuthenticationToken;
023    import org.apache.shiro.subject.Subject;
024    import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
025    import org.apache.shiro.web.util.WebUtils;
026    import org.slf4j.Logger;
027    import org.slf4j.LoggerFactory;
028    
029    import javax.servlet.ServletRequest;
030    import javax.servlet.ServletResponse;
031    import javax.servlet.http.HttpServletRequest;
032    import java.io.IOException;
033    
034    /**
035     * This filter validates the CAS service ticket to authenticate the user.  It must be configured on the URL recognized
036     * by the CAS server.  For example, in {@code shiro.ini}:
037     * <pre>
038     * [main]
039     * casFilter = org.apache.shiro.cas.CasFilter
040     * ...
041     *
042     * [urls]
043     * /shiro-cas = casFilter
044     * ...
045     * </pre>
046     * (example : http://host:port/mycontextpath/shiro-cas)
047     *
048     * @since 1.2
049     */
050    public class CasFilter extends AuthenticatingFilter {
051        
052        private static Logger logger = LoggerFactory.getLogger(CasFilter.class);
053        
054        // the name of the parameter service ticket in url
055        private static final String TICKET_PARAMETER = "ticket";
056        
057        // the url where the application is redirected if the CAS service ticket validation failed (example : /mycontextpatch/cas_error.jsp)
058        private String failureUrl;
059        
060        /**
061         * The token created for this authentication is a CasToken containing the CAS service ticket received on the CAS service url (on which
062         * the filter must be configured).
063         * 
064         * @param request the incoming request
065         * @param response the outgoing response
066         * @throws Exception if there is an error processing the request.
067         */
068        @Override
069        protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
070            HttpServletRequest httpRequest = (HttpServletRequest) request;
071            String ticket = httpRequest.getParameter(TICKET_PARAMETER);
072            return new CasToken(ticket);
073        }
074        
075        /**
076         * Execute login by creating {@link #createToken(javax.servlet.ServletRequest, javax.servlet.ServletResponse) token} and logging subject
077         * with this token.
078         * 
079         * @param request the incoming request
080         * @param response the outgoing response
081         * @throws Exception if there is an error processing the request.
082         */
083        @Override
084        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
085            return executeLogin(request, response);
086        }
087        
088        /**
089         * Returns <code>false</code> to always force authentication (user is never considered authenticated by this filter).
090         * 
091         * @param request the incoming request
092         * @param response the outgoing response
093         * @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings.
094         * @return <code>false</code>
095         */
096        @Override
097        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
098            return false;
099        }
100        
101        /**
102         * If login has been successful, redirect user to the original protected url.
103         * 
104         * @param token the token representing the current authentication
105         * @param subject the current authenticated subjet
106         * @param request the incoming request
107         * @param response the outgoing response
108         * @throws Exception if there is an error processing the request.
109         */
110        @Override
111        protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,
112                                         ServletResponse response) throws Exception {
113            issueSuccessRedirect(request, response);
114            return false;
115        }
116        
117        /**
118         * If login has failed, redirect user to the CAS error page (no ticket or ticket validation failed) except if the user is already
119         * authenticated, in which case redirect to the default success url.
120         * 
121         * @param token the token representing the current authentication
122         * @param ae the current authentication exception
123         * @param request the incoming request
124         * @param response the outgoing response
125         */
126        @Override
127        protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request,
128                                         ServletResponse response) {
129            // is user authenticated or in remember me mode ?
130            Subject subject = getSubject(request, response);
131            if (subject.isAuthenticated() || subject.isRemembered()) {
132                try {
133                    issueSuccessRedirect(request, response);
134                } catch (Exception e) {
135                    logger.error("Cannot redirect to the default success url", e);
136                }
137            } else {
138                try {
139                    WebUtils.issueRedirect(request, response, failureUrl);
140                } catch (IOException e) {
141                    logger.error("Cannot redirect to failure url : {}", failureUrl, e);
142                }
143            }
144            return false;
145        }
146        
147        public void setFailureUrl(String failureUrl) {
148            this.failureUrl = failureUrl;
149        }
150    }