View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.security.jaspi.modules;
20  
21  import java.io.IOException;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import javax.security.auth.Subject;
26  import javax.security.auth.callback.CallbackHandler;
27  import javax.security.auth.callback.UnsupportedCallbackException;
28  import javax.security.auth.message.AuthException;
29  import javax.security.auth.message.AuthStatus;
30  import javax.security.auth.message.MessageInfo;
31  import javax.security.auth.message.MessagePolicy;
32  import javax.servlet.http.HttpServletRequest;
33  import javax.servlet.http.HttpServletResponse;
34  import javax.servlet.http.HttpSession;
35  
36  import org.eclipse.jetty.security.authentication.DeferredAuthentication;
37  import org.eclipse.jetty.security.authentication.LoginCallbackImpl;
38  import org.eclipse.jetty.security.authentication.SessionAuthentication;
39  import org.eclipse.jetty.server.UserIdentity;
40  import org.eclipse.jetty.util.StringUtil;
41  import org.eclipse.jetty.util.URIUtil;
42  import org.eclipse.jetty.util.log.Log;
43  import org.eclipse.jetty.util.log.Logger;
44  import org.eclipse.jetty.util.security.Constraint;
45  import org.eclipse.jetty.util.security.Password;
46  
47  @Deprecated
48  public class FormAuthModule extends BaseAuthModule
49  {
50      private static final Logger LOG = Log.getLogger(FormAuthModule.class);
51  
52      /* ------------------------------------------------------------ */
53      public final static String __J_URI = "org.eclipse.jetty.util.URI";
54  
55      public final static String __J_AUTHENTICATED = "org.eclipse.jetty.server.Auth";
56  
57      public final static String __J_SECURITY_CHECK = "/j_security_check";
58  
59      public final static String __J_USERNAME = "j_username";
60  
61      public final static String __J_PASSWORD = "j_password";
62  
63      // private String realmName;
64      public static final String LOGIN_PAGE_KEY = "org.eclipse.jetty.security.jaspi.modules.LoginPage";
65  
66      public static final String ERROR_PAGE_KEY = "org.eclipse.jetty.security.jaspi.modules.ErrorPage";
67  
68      public static final String SSO_SOURCE_KEY = "org.eclipse.jetty.security.jaspi.modules.SsoSource";
69  
70      private String _formErrorPage;
71  
72      private String _formErrorPath;
73  
74      private String _formLoginPage;
75  
76      private String _formLoginPath;
77  
78  
79      public FormAuthModule()
80      {
81      }
82  
83      public FormAuthModule(CallbackHandler callbackHandler, String loginPage, String errorPage)
84      {
85          super(callbackHandler);
86          setLoginPage(loginPage);
87          setErrorPage(errorPage);
88      }
89  
90  
91      @Override
92      public void initialize(MessagePolicy requestPolicy, MessagePolicy responsePolicy, 
93                             CallbackHandler handler, Map options) 
94      throws AuthException
95      {
96          super.initialize(requestPolicy, responsePolicy, handler, options);
97          setLoginPage((String) options.get(LOGIN_PAGE_KEY));
98          setErrorPage((String) options.get(ERROR_PAGE_KEY));
99      }
100 
101     private void setLoginPage(String path)
102     {
103         if (!path.startsWith("/"))
104         {
105             LOG.warn("form-login-page must start with /");
106             path = "/" + path;
107         }
108         _formLoginPage = path;
109         _formLoginPath = path;
110         if (_formLoginPath.indexOf('?') > 0) _formLoginPath = _formLoginPath.substring(0, _formLoginPath.indexOf('?'));
111     }
112 
113     /* ------------------------------------------------------------ */
114     private void setErrorPage(String path)
115     {
116         if (path == null || path.trim().length() == 0)
117         {
118             _formErrorPath = null;
119             _formErrorPage = null;
120         }
121         else
122         {
123             if (!path.startsWith("/"))
124             {
125                 LOG.warn("form-error-page must start with /");
126                 path = "/" + path;
127             }
128             _formErrorPage = path;
129             _formErrorPath = path;
130 
131             if (_formErrorPath.indexOf('?') > 0) _formErrorPath = _formErrorPath.substring(0, _formErrorPath.indexOf('?'));
132         }
133     }
134 
135     @Override
136     public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException
137     {
138        
139         HttpServletRequest request = (HttpServletRequest) messageInfo.getRequestMessage();
140         HttpServletResponse response = (HttpServletResponse) messageInfo.getResponseMessage();
141         String uri = request.getRequestURI();
142         if (uri==null)
143             uri=URIUtil.SLASH;
144         
145         boolean mandatory = isMandatory(messageInfo);  
146         mandatory |= isJSecurityCheck(uri);
147         HttpSession session = request.getSession(mandatory);
148         
149         // not mandatory or its the login or login error page don't authenticate
150         if (!mandatory || isLoginOrErrorPage(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()))) 
151             return AuthStatus.SUCCESS;  // TODO return null for do nothing?
152 
153         try
154         {
155             // Handle a request for authentication.
156             if (isJSecurityCheck(uri))
157             {
158                 final String username = request.getParameter(__J_USERNAME);
159                 final String password = request.getParameter(__J_PASSWORD);
160             
161                 boolean success = tryLogin(messageInfo, clientSubject, response, session, username, new Password(password));
162                 if (success)
163                 {
164                     // Redirect to original request                    
165                     String nuri=null;
166                     synchronized(session)
167                     {
168                         nuri = (String) session.getAttribute(__J_URI);
169                     }
170                     
171                     if (nuri == null || nuri.length() == 0)
172                     {
173                         nuri = request.getContextPath();
174                         if (nuri.length() == 0) 
175                             nuri = URIUtil.SLASH;
176                     }
177                    
178                     response.setContentLength(0);   
179                     response.sendRedirect(response.encodeRedirectURL(nuri));
180                     return AuthStatus.SEND_CONTINUE;
181                 }
182                 // not authenticated
183                 if (LOG.isDebugEnabled()) LOG.debug("Form authentication FAILED for " + StringUtil.printable(username));
184                 if (_formErrorPage == null)
185                 {
186                     if (response != null) response.sendError(HttpServletResponse.SC_FORBIDDEN);
187                 }
188                 else
189                 {
190                     response.setContentLength(0);
191                     response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formErrorPage)));
192                 }
193                 // TODO is this correct response if isMandatory false??? Can
194                 // that occur?
195                 return AuthStatus.SEND_FAILURE;
196             }
197             
198             
199             // Check if the session is already authenticated.
200             SessionAuthentication sessionAuth = (SessionAuthentication)session.getAttribute(SessionAuthentication.__J_AUTHENTICATED);
201             if (sessionAuth != null)
202             {                
203                 //TODO: ideally we would like the form auth module to be able to invoke the 
204                 //loginservice.validate() method to check the previously authed user, but it is not visible
205                 //to FormAuthModule
206                 if (sessionAuth.getUserIdentity().getSubject() == null)
207                     return AuthStatus.SEND_FAILURE;
208 
209                 Set<Object> credentials = sessionAuth.getUserIdentity().getSubject().getPrivateCredentials();
210                 if (credentials == null || credentials.isEmpty())
211                     return AuthStatus.SEND_FAILURE; //if no private credentials, assume it cannot be authenticated
212 
213                 clientSubject.getPrivateCredentials().addAll(credentials);
214                 clientSubject.getPrivateCredentials().add(sessionAuth.getUserIdentity());
215 
216                 return AuthStatus.SUCCESS;  
217             }
218             
219 
220             // if we can't send challenge
221             if (DeferredAuthentication.isDeferred(response))
222                 return AuthStatus.SUCCESS; 
223             
224 
225             // redirect to login page  
226             StringBuffer buf = request.getRequestURL();
227             if (request.getQueryString() != null)
228                 buf.append("?").append(request.getQueryString());
229 
230             synchronized (session)
231             {
232                 session.setAttribute(__J_URI, buf.toString());
233             }
234             
235             response.setContentLength(0);
236             response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getContextPath(), _formLoginPage)));
237             return AuthStatus.SEND_CONTINUE;
238         }
239         catch (IOException e)
240         {
241             throw new AuthException(e.getMessage());
242         }
243         catch (UnsupportedCallbackException e)
244         {
245             throw new AuthException(e.getMessage());
246         }
247 
248     }
249     
250     /* ------------------------------------------------------------ */
251     public boolean isJSecurityCheck(String uri)
252     {
253         int jsc = uri.indexOf(__J_SECURITY_CHECK);
254         
255         if (jsc<0)
256             return false;
257         int e=jsc+__J_SECURITY_CHECK.length();
258         if (e==uri.length())
259             return true;
260         char c = uri.charAt(e);
261         return c==';'||c=='#'||c=='/'||c=='?';
262     }
263 
264     private boolean tryLogin(MessageInfo messageInfo, Subject clientSubject, 
265                              HttpServletResponse response, HttpSession session, 
266                              String username, Password password) 
267     throws AuthException, IOException, UnsupportedCallbackException
268     {
269         if (login(clientSubject, username, password, Constraint.__FORM_AUTH, messageInfo))
270         {
271             char[] pwdChars = password.toString().toCharArray();
272             Set<LoginCallbackImpl> loginCallbacks = clientSubject.getPrivateCredentials(LoginCallbackImpl.class);
273            
274             if (!loginCallbacks.isEmpty())
275             {
276                 LoginCallbackImpl loginCallback = loginCallbacks.iterator().next();
277                 Set<UserIdentity> userIdentities = clientSubject.getPrivateCredentials(UserIdentity.class);
278                 if (!userIdentities.isEmpty())
279                 {
280                     UserIdentity userIdentity = userIdentities.iterator().next();
281                    
282                 SessionAuthentication sessionAuth = new SessionAuthentication(Constraint.__FORM_AUTH, userIdentity, password);
283                 session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, sessionAuth);
284                 }
285             }
286 
287             return true;
288         }
289         return false;
290     }
291 
292     public boolean isLoginOrErrorPage(String pathInContext)
293     {
294         return pathInContext != null && (pathInContext.equals(_formErrorPath) || pathInContext.equals(_formLoginPath));
295     }
296 
297 }