View Javadoc

1   /* Copyright 2004, 2005 Acegi Technology Pty Limited
2    *
3    * Licensed under the Apache License, Version 2.0 (the "License");
4    * you may not use this file except in compliance with the License.
5    * You may obtain a copy of the License at
6    *
7    *     http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  
16  package org.acegisecurity.ui.basicauth;
17  
18  import org.acegisecurity.Authentication;
19  import org.acegisecurity.AuthenticationException;
20  import org.acegisecurity.AuthenticationManager;
21  import org.acegisecurity.context.SecurityContextHolder;
22  import org.acegisecurity.intercept.web.AuthenticationEntryPoint;
23  import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
24  import org.acegisecurity.ui.WebAuthenticationDetails;
25  
26  import org.apache.commons.codec.binary.Base64;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  
30  import org.springframework.beans.factory.InitializingBean;
31  
32  import org.springframework.util.Assert;
33  
34  import java.io.IOException;
35  
36  import javax.servlet.Filter;
37  import javax.servlet.FilterChain;
38  import javax.servlet.FilterConfig;
39  import javax.servlet.ServletException;
40  import javax.servlet.ServletRequest;
41  import javax.servlet.ServletResponse;
42  import javax.servlet.http.HttpServletRequest;
43  import javax.servlet.http.HttpServletResponse;
44  
45  
46  /***
47   * Processes a HTTP request's BASIC authorization headers, putting the result
48   * into the <code>SecurityContextHolder</code>.
49   * 
50   * <p>
51   * For a detailed background on what this filter is designed to process, refer
52   * to <A HREF="http://www.faqs.org/rfcs/rfc1945.html">RFC 1945, Section
53   * 11.1</A>. Any realm name presented in the HTTP request is ignored.
54   * </p>
55   * 
56   * <p>
57   * In summary, this filter is responsible for processing any request that has a
58   * HTTP request header of <code>Authorization</code> with an authentication
59   * scheme of <code>Basic</code> and a Base64-encoded
60   * <code>username:password</code> token. For example, to authenticate user
61   * "Aladdin" with password "open sesame" the following header would be
62   * presented:
63   * </p>
64   * 
65   * <p>
66   * <code>Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==</code>.
67   * </p>
68   * 
69   * <p>
70   * This filter can be used to provide BASIC authentication services to both
71   * remoting protocol clients (such as Hessian and SOAP) as well as standard
72   * user agents (such as Internet Explorer and Netscape).
73   * </p>
74   * 
75   * <P>
76   * If authentication is successful, the resulting {@link Authentication} object
77   * will be placed into the <code>SecurityContextHolder</code>.
78   * </p>
79   * 
80   * <p>
81   * If authentication fails, an {@link AuthenticationEntryPoint} implementation
82   * is called. Usually this should be {@link BasicProcessingFilterEntryPoint},
83   * which will prompt the user to authenticate again via BASIC authentication.
84   * </p>
85   * 
86   * <p>
87   * Basic authentication is an attractive protocol because it is simple and
88   * widely deployed. However, it still transmits a password in clear text and
89   * as such is undesirable in many situations. Digest authentication is also
90   * provided by Acegi Security and should be used instead of Basic
91   * authentication wherever possible. See {@link
92   * org.acegisecurity.ui.digestauth.DigestProcessingFilter}.
93   * </p>
94   * 
95   * <p>
96   * <b>Do not use this class directly.</b> Instead configure
97   * <code>web.xml</code> to use the {@link
98   * org.acegisecurity.util.FilterToBeanProxy}.
99   * </p>
100  *
101  * @author Ben Alex
102  * @version $Id: BasicProcessingFilter.java,v 1.16 2005/11/17 00:56:48 benalex Exp $
103  */
104 public class BasicProcessingFilter implements Filter, InitializingBean {
105     //~ Static fields/initializers =============================================
106 
107     private static final Log logger = LogFactory.getLog(BasicProcessingFilter.class);
108 
109     //~ Instance fields ========================================================
110 
111     private AuthenticationEntryPoint authenticationEntryPoint;
112     private AuthenticationManager authenticationManager;
113 
114     //~ Methods ================================================================
115 
116     public void setAuthenticationEntryPoint(
117         AuthenticationEntryPoint authenticationEntryPoint) {
118         this.authenticationEntryPoint = authenticationEntryPoint;
119     }
120 
121     public AuthenticationEntryPoint getAuthenticationEntryPoint() {
122         return authenticationEntryPoint;
123     }
124 
125     public void setAuthenticationManager(
126         AuthenticationManager authenticationManager) {
127         this.authenticationManager = authenticationManager;
128     }
129 
130     public AuthenticationManager getAuthenticationManager() {
131         return authenticationManager;
132     }
133 
134     public void afterPropertiesSet() throws Exception {
135         Assert.notNull(this.authenticationManager,
136             "An AuthenticationManager is required");
137         Assert.notNull(this.authenticationEntryPoint,
138             "An AuthenticationEntryPoint is required");
139     }
140 
141     public void destroy() {}
142 
143     public void doFilter(ServletRequest request, ServletResponse response,
144         FilterChain chain) throws IOException, ServletException {
145         if (!(request instanceof HttpServletRequest)) {
146             throw new ServletException("Can only process HttpServletRequest");
147         }
148 
149         if (!(response instanceof HttpServletResponse)) {
150             throw new ServletException("Can only process HttpServletResponse");
151         }
152 
153         HttpServletRequest httpRequest = (HttpServletRequest) request;
154 
155         String header = httpRequest.getHeader("Authorization");
156 
157         if (logger.isDebugEnabled()) {
158             logger.debug("Authorization header: " + header);
159         }
160 
161         if ((header != null) && header.startsWith("Basic ")) {
162             String base64Token = header.substring(6);
163             String token = new String(Base64.decodeBase64(
164                         base64Token.getBytes()));
165 
166             String username = "";
167             String password = "";
168             int delim = token.indexOf(":");
169 
170             if (delim != -1) {
171                 username = token.substring(0, delim);
172                 password = token.substring(delim + 1);
173             }
174 
175             // Only reauthenticate if username doesn't match SecurityContextHolder and user isn't authenticated (see SEC-53)
176             Authentication existingAuth = SecurityContextHolder.getContext()
177                                                                .getAuthentication();
178 
179             if ((existingAuth == null)
180                 || !existingAuth.getName().equals(username)
181                 || !existingAuth.isAuthenticated()) {
182                 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username,
183                         password);
184                 authRequest.setDetails(new WebAuthenticationDetails(
185                         httpRequest, false));
186 
187                 Authentication authResult;
188 
189                 try {
190                     authResult = authenticationManager.authenticate(authRequest);
191                 } catch (AuthenticationException failed) {
192                     // Authentication failed
193                     if (logger.isDebugEnabled()) {
194                         logger.debug("Authentication request for user: "
195                             + username + " failed: " + failed.toString());
196                     }
197 
198                     SecurityContextHolder.getContext().setAuthentication(null);
199                     authenticationEntryPoint.commence(request, response, failed);
200 
201                     return;
202                 }
203 
204                 // Authentication success
205                 if (logger.isDebugEnabled()) {
206                     logger.debug("Authentication success: "
207                         + authResult.toString());
208                 }
209 
210                 SecurityContextHolder.getContext().setAuthentication(authResult);
211             }
212         }
213 
214         chain.doFilter(request, response);
215     }
216 
217     public void init(FilterConfig arg0) throws ServletException {}
218 }