JAAS based username/password authentication
authorlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Sat, 28 Jul 2007 13:52:50 +0000 (13:52 +0000)
committerlajoie <lajoie@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Sat, 28 Jul 2007 13:52:50 +0000 (13:52 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@2324 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

resources/classpath/schema/shibboleth-2.0-idp-profile-handler.xsd
resources/webpages/login.jsp [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/idp/authn/provider/RemoteUserAuthenticationHandler.java
src/edu/internet2/middleware/shibboleth/idp/authn/provider/UsernamePasswordAuthenticationHandler.java
src/edu/internet2/middleware/shibboleth/idp/authn/provider/UsernamePasswordAuthenticationServlet.java
src/edu/internet2/middleware/shibboleth/idp/config/profile/authn/UsernamePasswordAuthenticationHandlerBeanDefinitionParser.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/idp/config/profile/authn/UsernamePasswordAuthenticationHandlerFactoryBean.java [new file with mode: 0644]

index 8a91927..8c123c3 100644 (file)
@@ -46,7 +46,7 @@
             <xsd:extension base="RequestURIMappedProfileHandlerType" />
         </xsd:complexContent>
     </xsd:complexType>
-    
+
     <xsd:complexType name="SAML2SSO">
         <xsd:annotation>
             <xsd:documentation>Configuration type for SAML 2 SSO profile handlers.</xsd:documentation>
                         </xsd:documentation>
                     </xsd:annotation>
                 </xsd:attribute>
-                <xsd:attribute name="outboundBindingEnumeration" default="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST">
+                <xsd:attribute name="outboundBindingEnumeration"
+                    default="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST">
                     <xsd:annotation>
                         <xsd:documentation>
-                            An ordered list of outbound bindings supported by this profile handler.  The order
-                            provided establishs the precedence given the bindings such that, from the left to right, 
-                            the first binding also supported by the relying party will be used.
+                            An ordered list of outbound bindings supported by this profile handler. The order provided
+                            establishs the precedence given the bindings such that, from the left to right, the first
+                            binding also supported by the relying party will be used.
                         </xsd:documentation>
                     </xsd:annotation>
                     <xsd:simpleType>
-                        <xsd:list itemType="xsd:anyURI"/>
+                        <xsd:list itemType="xsd:anyURI" />
                     </xsd:simpleType>
                 </xsd:attribute>
             </xsd:extension>
                         </xsd:documentation>
                     </xsd:annotation>
                 </xsd:attribute>
-                <xsd:attribute name="outboundBindingEnumeration" default="urn:oasis:names:tc:SAML:1.0:profiles:browser-post">
+                <xsd:attribute name="outboundBindingEnumeration"
+                    default="urn:oasis:names:tc:SAML:1.0:profiles:browser-post">
                     <xsd:annotation>
                         <xsd:documentation>
-                            An ordered list of outbound bindings supported by this profile handler.  The order
-                            provided establishs the precedence given the bindings such that, from the left to right, 
-                            the first binding also supported by the relying party will be used.
+                            An ordered list of outbound bindings supported by this profile handler. The order provided
+                            establishs the precedence given the bindings such that, from the left to right, the first
+                            binding also supported by the relying party will be used.
                         </xsd:documentation>
                     </xsd:annotation>
                     <xsd:simpleType>
-                        <xsd:list itemType="xsd:anyURI"/>
+                        <xsd:list itemType="xsd:anyURI" />
                     </xsd:simpleType>
                 </xsd:attribute>
             </xsd:extension>
         </xsd:complexContent>
     </xsd:complexType>
-    
+
     <xsd:complexType name="SAML1AttributeQuery">
         <xsd:annotation>
             <xsd:documentation>Configuration type for SAML 1 Attribute Query profile handlers.</xsd:documentation>
         </xsd:complexContent>
     </xsd:complexType>
 
+    <xsd:complexType name="UsernamePassword">
+        <xsd:complexContent>
+            <xsd:extension base="AuthenticationHandlerType">
+                <xsd:attribute name="jaasConfigurationLocation" type="xsd:anyURI">
+                    <xsd:annotation>
+                        <xsd:documentation>
+                            Location of the JAAS configuration. If this attribute is used it will usually contain a file
+                            URL to a configuration on the local filesystem. However, this attribute need not be used and
+                            this information can be set within the VM in any manner supported by the JVM/container
+                            implementation.
+                        </xsd:documentation>
+                    </xsd:annotation>
+                </xsd:attribute>
+                <xsd:attribute name="protectedServletPath" type="xsd:string" default="/Authn/UserPassword">
+                    <xsd:annotation>
+                        <xsd:documentation>
+                            The servlet context path to the
+                            edu.internet2.middleware.shibboleth.idp.authn.provider.UsernamePasswordAuthenticationServlet
+                            that will authenticate the user.
+                        </xsd:documentation>
+                    </xsd:annotation>
+                </xsd:attribute>
+            </xsd:extension>
+        </xsd:complexContent>
+    </xsd:complexType>
+
     <xsd:complexType name="AuthenticationHandlerType" abstract="true">
         <xsd:annotation>
             <xsd:documentation>Base type for authentication handler types.</xsd:documentation>
diff --git a/resources/webpages/login.jsp b/resources/webpages/login.jsp
new file mode 100644 (file)
index 0000000..0ded682
--- /dev/null
@@ -0,0 +1,11 @@
+<html>
+
+       <body>
+               <form method="POST" action="${login.endpoint}">
+                       <input name="j_username" type="text" tabindex="1" />
+                       <input name="j_password" type="password" tabindex="2" />
+                       <button tabindex="3"/>
+               </form>
+       </body>
+       
+</html>
\ No newline at end of file
index 0b2a8b3..b932f9e 100644 (file)
@@ -78,7 +78,7 @@ public class RemoteUserAuthenticationHandler extends AbstractAuthenticationHandl
             httpResponse.sendRedirect(urlBuilder.buildURL());
             return;
         } catch (IOException ex) {
-            log.error("RemoteUserAuthenticationHandler: Unable to redirect to remote user authentication servlet.", ex);
+            log.error("Unable to redirect to remote user authentication servlet.", ex);
         }
     }
 }
\ No newline at end of file
index f3d564d..3978cf4 100644 (file)
@@ -18,21 +18,16 @@ package edu.internet2.middleware.shibboleth.idp.authn.provider;
 
 import java.io.IOException;
 
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationHandler;
 
 import org.apache.log4j.Logger;
-import org.joda.time.DateTime;
+import org.opensaml.util.URLBuilder;
 
 /**
  * Authenticate a username and password against a JAAS source.
  * 
- * This {@link AuthenticationHandler} requires a JSP to collect a username and password from the user. It also requires
+ * This authenticaiton handler requires a JSP to collect a username and password from the user. It also requires
  * a JAAS configuration file to validate the username and password.
  * 
  * If an Authentication Context Class or DeclRef URI is not specified, it will default to
@@ -40,110 +35,50 @@ import org.joda.time.DateTime;
  */
 public class UsernamePasswordAuthenticationHandler extends AbstractAuthenticationHandler {
 
-    /** Key in an HttpSession for the JAAS configuration name. */
-    public static final String JAAS_CONFIG_NAME = "UsernamePasswordAuthenticationHandler.JAAS_CONFIG_NAME";
-
-    /** Key in an HttpSession for the username. */
-    public static final String USERNAME = "UsernamePasswordAuthenticationHandler.USERNAME";
-
-    /** Key in an HttpSession for the authentication instant. */
-    public static final String AUTHN_INSTANT = "UsernamePasswordAuthenticationHandler.AUTHN_INSTANT";
-
-    private static final Logger log = Logger.getLogger(UsernamePasswordAuthenticationHandler.class);
-
-    /** The name of the JAAS Configuration to use. */
-    protected String jaasConfigurationName;
-
-    /** The name of the login page. */
-    protected String loginURL;
-
-    /** The authN duration, in seconds. */
-    protected int authnDuration;
-
-    /** The URI of the AuthnContextDeclRef or the AuthnContextClass */
-    private String authnMethodURI = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport";
-
-    public UsernamePasswordAuthenticationHandler() {
-    }
+    /** Class logger. */
+    private final Logger log = Logger.getLogger(UsernamePasswordAuthenticationHandler.class);
 
-    /** @{inheritDoc} */
-    public boolean supportsPassive() {
-        return true;
-    }
+    /** The URL of the servlet used to perform authentication. */
+    private String authenticationServletURL;
 
-    /** @{inheritDoc} */
-    public boolean supportsForceAuthentication() {
-        return true;
+    /** 
+     * Constructor.
+     *
+     * @param servletURL URL to the authentication servlet
+     */
+    public UsernamePasswordAuthenticationHandler(String servletURL){
+        super();
+        setSupportsPassive(false);
+        setSupportsForceAuthentication(true);
+        authenticationServletURL = servletURL;
     }
 
     /** {@inheritDoc} */
-    public void login(
-            final HttpServletRequest httpRequest, final HttpServletResponse httpResponse) {
-
-//        HttpSession session = httpRequest.getSession();
-//
-//        // these fields will need to be set, regardless of how we branch.
-//        loginContext.setAuthenticationAttempted();
-//        loginContext.setAuthenticationMethod(authnMethodURI);
-//
-//        // If forceAuth is set, we must forward to the login JSP.
-//        if (loginContext.getForceAuth()) {
-//
-//            if (loginContext.getPassiveAuth()) {
-//                log
-//                        .error("UsernamePasswordAuthenticationHandler: Unable to authenticate user: both forceAuthN and passiveAuthnN are set in the login context.");
-//                redirectControl(loginContext.getAuthenticationEngineURL(), "AuthenticationManager", httpRequest,
-//                        httpResponse);
-//            }
-//
-//            session.setAttribute(JAAS_CONFIG_NAME, jaasConfigurationName);
-//            redirectControl(loginURL, "login page", httpRequest, httpResponse);
-//        }
-//
-//        // If the user has already been authenticated, forceAuth is not set,
-//        // and the authentication hasn't expired, then populate the LoginCtx
-//        // and return control to the AuthenticationManager.
-//        // Otherwise, redirect the user to loginJSPURL to collect a username and
-//        // password.
-//
-//        // implementation note: There is a race condition here, but I'm not sure
-//        // how to avoid it. I need a way to instantiate a lock in the session to
-//        // protect the
-//        // username and authnInstant fields.
-//
-//        Object o = session.getAttribute(USERNAME);
-//        if (!(o instanceof String)) {
-//            log
-//                    .debug("UsernamePasswordAuthenticationHandler: Username attribute found in HttpSession, but it is not a String.");
-//
-//            redirectControl(loginURL, "login page", httpRequest, httpResponse);
-//        }
-//
-//        String username = (String) o;
-//
-//        o = session.getAttribute(AUTHN_INSTANT);
-//        if (!(o instanceof DateTime)) {
-//            log.debug("UsernamePasswordAuthenticationHandler: AuthnInstant attribute found in HttpSession for user "
-//                    + username + ", but it is not a DateTime.");
-//
-//            redirectControl(loginURL, "login page", httpRequest, httpResponse);
-//        }
-//
-//        DateTime authnInstant = (DateTime) o;
-//        DateTime authnExpires = authnInstant.plusSeconds(authnDuration);
-//        DateTime now = new DateTime();
-//        if (now.isAfter(authnExpires)) {
-//            log.info("UsernamePasswordAuthenticationHandler: Authentication has expired for user " + username);
-//            redirectControl(loginURL, "login page", httpRequest, httpResponse);
-//        }
-//
-//        // the current authentication information is still valid, so return it.
-//        loginContext.setPrincipalAuthenticated(true);
-//        loginContext.setPrincipalName(username);
-//        loginContext.setAuthenticationInstant(authnInstant);
-//
-//        // XXX: adjust for the appropriate units?
-//        loginContext.setAuthenticationDuration(authnDuration);
+    public void login(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse) {
+        // forward control to the servlet.
+        try {
+            StringBuilder pathBuilder = new StringBuilder();
+            pathBuilder.append(httpRequest.getContextPath());
+            if (!authenticationServletURL.startsWith("/")) {
+                pathBuilder.append("/");
+            }
+            pathBuilder.append(authenticationServletURL);
+
+            URLBuilder urlBuilder = new URLBuilder();
+            urlBuilder.setScheme(httpRequest.getScheme());
+            urlBuilder.setHost(httpRequest.getLocalName());
+            urlBuilder.setPort(httpRequest.getLocalPort());
+            urlBuilder.setPath(pathBuilder.toString());
+
+            if (log.isDebugEnabled()) {
+                log.debug("Redirecting to " + urlBuilder.buildURL());
+            }
+
+            httpResponse.sendRedirect(urlBuilder.buildURL());
+            return;
+        } catch (IOException ex) {
+            log.error("Unable to redirect to authentication servlet.", ex);
+        }
 
     }
 
@@ -151,61 +86,4 @@ public class UsernamePasswordAuthenticationHandler extends AbstractAuthenticatio
     public void logout(final HttpServletRequest request, final HttpServletResponse response, String principal) {
         return;
     }
-
-    /**
-     * Set the name of the JAAS Configuration to use for user authentication.
-     * 
-     * @param configurationName The name of the JAAS Configuration entry.
-     */
-    public void setJAASConfigurationName(String configurationName) {
-        jaasConfigurationName = configurationName;
-    }
-
-    /**
-     * Get the name of the JAAS Configuraiton to use for user authentication.
-     * 
-     * @return The name of the JAAS Configuration entry.
-     */
-    public String getJAASConfiguraitonName() {
-        return jaasConfigurationName;
-    }
-
-    /**
-     * Set the duration of the authentication.
-     * 
-     * @param duration The duration, in seconds, of the authentication.
-     */
-    public void setAuthNDuration(int duration) {
-        authnDuration = duration;
-    }
-
-    /**
-     * Get the duration of the authentication.
-     * 
-     * @return The duration, in seconds, of the authentication.
-     */
-    public int getAuthNDuration() {
-        return authnDuration;
-    }
-
-    /**
-     * Return control to the AuthNManager.
-     * 
-     * @param url The URL to which control should be redirected.
-     * @param urlDescription An optional textual description of <code>url</code>.
-     * @param request The HttpServletRequest.
-     * @param response The HttpServletResponse.
-     */
-    protected void redirectControl(String url, String urlDescription, final HttpServletRequest request,
-            final HttpServletResponse response) {
-
-        try {
-            RequestDispatcher dispatcher = request.getRequestDispatcher(url);
-            dispatcher.forward(request, response);
-        } catch (ServletException ex) {
-            log.error("UsernamePasswordAuthenticationHandler: Error returning control to " + urlDescription, ex);
-        } catch (IOException ex) {
-            log.error("UsernamePasswordAuthenticationHandler: Error returning control to " + urlDescription, ex);
-        }
-    }
-}
+}
\ No newline at end of file
index 187608f..a6bce00 100644 (file)
@@ -17,9 +17,7 @@
 package edu.internet2.middleware.shibboleth.idp.authn.provider;
 
 import java.io.IOException;
-
 import java.security.Principal;
-import java.util.Set;
 
 import javax.security.auth.Subject;
 import javax.security.auth.callback.Callback;
@@ -28,237 +26,172 @@ import javax.security.auth.callback.NameCallback;
 import javax.security.auth.callback.PasswordCallback;
 import javax.security.auth.callback.UnsupportedCallbackException;
 import javax.security.auth.login.LoginException;
-
-import javax.servlet.RequestDispatcher;
-import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
 
 import org.apache.log4j.Logger;
-import org.joda.time.DateTime;
+import org.opensaml.util.URLBuilder;
+import org.opensaml.xml.util.DatatypeHelper;
+
+import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationEngine;
+import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationHandler;
 
 /**
- * This servlet should be protected by a filter which populates REMOTE_USER. The
- * serlvet will then set the remote user field in a LoginContext.
+ * This servlet should be protected by a filter which populates REMOTE_USER. The serlvet will then set the remote user
+ * field in a LoginContext.
  */
 public class UsernamePasswordAuthenticationServlet extends HttpServlet {
 
-       // Implementation note:
-       // Pay attention to namespaces in this file. There are two classes named
-       // LoginContext.
-       // One is used by the IdP
-       // (edu.internet2.middleware.shibboleth.idp.authn.LoginContext).
-       // The other is used by JAAS (javax.security.auth.login.LoginContext).
-       //
-
-       /**
-        * Inner class to hold the username and password.
-        * 
-        * The web form will give us a username and password. We call out to a JAAS
-        * mechanism(s) to authenticate the user. This inner class implements the
-        * {@link CallbackHandler} interface to deliver the username and password to
-        * a JAAS {@link LoginModule}.
-        * 
-        * Note, this class only handles the {@link NameCallback} and
-        * {@link PasswordCallback}.
-        */
-       protected class SimpleCallbackHandler implements CallbackHandler {
-
-               private String uname;
-
-               private String pass;
-
-               /**
-                * @param username
-                *            The username
-                * @param password
-                *            The password
-                */
-               public SimpleCallbackHandler(String username, String password) {
-                       uname = username;
-                       pass = password;
-               }
+    /** Serial version UID. */
+    private static final long serialVersionUID = -572799841125956990L;
 
-               /**
-                * Handle a callback.
-                * 
-                * @param callbacks
-                *            The list of callbacks to process.
-                * 
-                * @throws UnsupportedCallbackException
-                *             If callbacks has a callback other than
-                *             {@link NameCallback} or {@link PasswordCallback}.
-                */
-               public void handle(final Callback[] callbacks)
-                               throws UnsupportedCallbackException {
+    /** Class logger. */
+    private final Logger log = Logger.getLogger(RemoteUserAuthServlet.class);
 
-                       if (callbacks == null || callbacks.length == 0) {
-                               return;
-                       }
+    /** Name of JAAS configuration used to authenticate users. */
+    private final String jaasConfigName = "ShibUserPassAuth";
+    
+    /** Login page name. */
+    private final String loginPage = "login.jsp";
 
-                       for (Callback cb : callbacks) {
-                               if (cb instanceof NameCallback) {
-                                       NameCallback ncb = (NameCallback) cb;
-                                       ncb.setName(uname);
-                               } else if (cb instanceof PasswordCallback) {
-                                       PasswordCallback pcb = (PasswordCallback) cb;
-                                       pcb.setPassword(pass.toCharArray());
-                               } else {
-                                       throw new UnsupportedCallbackException(cb,
-                                                       "This class only handles NameCallback and PasswordCallback");
-                               }
-                       }
-               }
-       }
+    /** HTTP request parameter containing the user name. */
+    private final String usernameAttribute = "j_username";
 
-       /** Login form element containing the username. */
-       protected static final String LOGIN_FORM_USERNAME = "username";
+    /** HTTP request parameter containing the user's password. */
+    private final String passwordAttribute = "j_password";
 
-       /** Login form element containing the password. */
-       protected static final String LOGIN_FORM_PASSWORD = "password";
+    /** {@inheritDoc} */
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
+        String username = DatatypeHelper.safeTrimOrNullString(request.getParameter(usernameAttribute));
+        String password = DatatypeHelper.safeTrimOrNullString(request.getParameter(passwordAttribute));
 
-       private static final Logger log = Logger
-                       .getLogger(RemoteUserAuthServlet.class);
-
-       public UsernamePasswordAuthenticationServlet() {
-       }
-
-       public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
-        
-        HttpSession httpSession = request.getSession();
-        
-        Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
-        if (!(o instanceof LoginContext)) {
-            log.error("RemoteUSerAuthServlet - Invalid login context object -- object is not an instance of LoginContext");
-            return; // where this will return to, I don't know.
+        if(username == null || password == null){
+            redirectToLoginPage(request, response);
+            return;
         }
         
-        LoginContext loginContext = (LoginContext)o;
-        
-        o = httpSession.getAttribute(UsernamePasswordAuthenticationHandler.JAAS_CONFIG_NAME);
-        httpSession.removeAttribute(UsernamePasswordAuthenticationHandler.JAAS_CONFIG_NAME);
-        if (!(o instanceof String)) {
-            log.error("UsernamePasswordAuthenticationServlet: Unable to authenticate user - Invalid JAAS configuration name specified: " + o.toString());
-            
-            loginContext.setPrincipalAuthenticated(false);
-            loginContext.setAuthenticationFailureMessage("Internal configuration error.");
-            redirectControl(loginContext.getAuthenticationEngineURL(), "AuthenticationManager", request, response);
+        if(authenticateUser(request)){
+            AuthenticationEngine.returnToAuthenticationEngine(request, response);
+        }else{
+            redirectToLoginPage(request, response);
+            return;
         }
-        
-        String jassConfiguration = (String)o;
-        
-        String username;
-        String password;
-        
-        o = request.getAttribute(LOGIN_FORM_USERNAME);
-        if (!(o instanceof String)) {
-            log.error("UsernamePasswordAuthenticationServlet: Login form's username is not a String.");
-            loginContext.setPrincipalAuthenticated(false);
-            loginContext.setAuthenticationFailureMessage("Internal configuration error.");
-            loginContext.setPrincipalAuthenticated(false);
-            loginContext.setAuthenticationFailureMessage("Internal configuration error.");
-            redirectControl(loginContext.getAuthenticationEngineURL(), "AuthenticationManager", request, response);
-            
-        }
-        username = (String)o;
-        
-        o = request.getAttribute(LOGIN_FORM_PASSWORD);
-        if (!(o instanceof String)) {
-            log.error("UsernamePasswordAuthenticationServlet: Login form's password is not a String.");
-            loginContext.setPrincipalAuthenticated(false);
-            loginContext.setAuthenticationFailureMessage("Internal configuration error.");
-            loginContext.setPrincipalAuthenticated(false);
-            loginContext.setAuthenticationFailureMessage("Internal configuration error.");
-            redirectControl(loginContext.getAuthenticationEngineURL(), "AuthenticationManager", request, response);
-            
+    }
+    
+    /**
+     * Sends the user to the login page.
+     * 
+     * @param request current request
+     * @param response current response
+     */
+    protected void redirectToLoginPage(HttpServletRequest request, HttpServletResponse response){
+        try {
+            StringBuilder pathBuilder = new StringBuilder();
+            pathBuilder.append(request.getContextPath());
+            pathBuilder.append("/");
+            pathBuilder.append(loginPage);
+
+            URLBuilder urlBuilder = new URLBuilder();
+            urlBuilder.setScheme(request.getScheme());
+            urlBuilder.setHost(request.getLocalName());
+            urlBuilder.setPort(request.getLocalPort());
+            urlBuilder.setPath(pathBuilder.toString());
+
+            if (log.isDebugEnabled()) {
+                log.debug("Redirecting to login page " + urlBuilder.buildURL());
+            }
+
+            response.sendRedirect(urlBuilder.buildURL());
+            return;
+        } catch (IOException ex) {
+            log.error("Unable to redirect to login page.", ex);
         }
-        password = (String)o;
-        
-        authenticateUser(username, password, jassConfiguration, loginContext);
-        password = null;
-        redirectControl(loginContext.getAuthenticationEngineURL(), "AuthenticationManager", request, response);
     }
 
-       /**
-        * Return control to the AuthNManager.
-        * 
-        * @param url
-        *            The URL to which control should be redirected.
-        * @param urlDescription
-        *            An optional textual description of <code>url</code>.
-        * @param request
-        *            The HttpServletRequest.
-        * @param response
-        *            The HttpServletResponse.
-        */
-       protected void redirectControl(String url, String urlDescription,
-                       final HttpServletRequest request, final HttpServletResponse response) {
-
-               try {
-                       RequestDispatcher dispatcher = request.getRequestDispatcher(url);
-                       dispatcher.forward(request, response);
-               } catch (ServletException ex) {
-                       log.error(
-                                       "UsernamePasswordAuthenticationServlet: Error returning control to "
-                                                       + urlDescription, ex);
-               } catch (IOException ex) {
-                       log.error(
-                                       "UsernamePasswordAuthenticationServlet: Error returning control to "
-                                                       + urlDescription, ex);
-               }
-       }
+    /**
+     * Authenticate a username and password against JAAS.  If authentication succeeds the principal name and 
+     * subject are placed into the request in their respective attributes.
+     * 
+     * @param request current authentication request
+     * 
+     * @return true of authentication succeeds, false if not
+     */
+    protected boolean authenticateUser(HttpServletRequest request) {
 
-       /**
-        * Authenticate a username and password against JAAS.
-        * 
-        * @param username
-        *            The username
-        * @param password
-        *            The password.
-        * @param jaasConfigurationName
-        *            The name of the JAAS configuration entry.
-        * @param idpLoginCtx
-        *            The authentication request's LoginContext
-        */
-       protected void authenticateUser(
-                       String username,
-                       String password,
-                       String jaasConfigurationName,
-                       final edu.internet2.middleware.shibboleth.idp.authn.LoginContext idpLoginCtx) {
+        try {
+            String username = DatatypeHelper.safeTrimOrNullString(request.getParameter(usernameAttribute));
+            String password = DatatypeHelper.safeTrimOrNullString(request.getParameter(passwordAttribute));
 
-               try {
-                       SimpleCallbackHandler cbh = new SimpleCallbackHandler(username,
-                                       password);
+            SimpleCallbackHandler cbh = new SimpleCallbackHandler(username, password);
 
-                       javax.security.auth.login.LoginContext jaasLoginCtx = new javax.security.auth.login.LoginContext(
-                                       jaasConfigurationName, cbh);
+            javax.security.auth.login.LoginContext jaasLoginCtx = new javax.security.auth.login.LoginContext(
+                    jaasConfigName, cbh);
 
-                       idpLoginCtx.setAuthenticationAttempted();
-                       idpLoginCtx.setAuthenticationInstant(new DateTime());
-
-                       jaasLoginCtx.login();
-                       log
-                                       .debug("UsernamePasswordAuthenticationServlet: Authentication successful for "
-                                                       + username);
-                       idpLoginCtx.setPrincipalAuthenticated(true);
+            jaasLoginCtx.login();
+            log.debug("Successfully authenticated user " + username);
+            
+            Subject subject = jaasLoginCtx.getSubject();
+            Principal principal = subject.getPrincipals().iterator().next();
+            request.setAttribute(AuthenticationHandler.PRINCIPAL_NAME_KEY, principal.getName());
+            request.setAttribute(AuthenticationHandler.SUBJECT_KEY, jaasLoginCtx.getSubject());
+
+            return true;
+        } catch (LoginException e) {
+            if (log.isDebugEnabled()) {
+                log.debug("User authentication failed", e);
+            }
+            return false;
+        }
+    }
 
-                       // if JAAS returned multiple usernames, only use the first one.
-                       Set<Principal> principals = jaasLoginCtx.getSubject()
-                                       .getPrincipals();
-                       Principal[] temp = new Principal[principals.size()];
-                       principals.toArray(temp);
-                       idpLoginCtx.setPrincipalName(temp[0].getName());
+    /**
+     * A callback handler that provides static name and password data to a JAAS loging process.
+     * 
+     * This handler only supports {@link NameCallback} and {@link PasswordCallback}.
+     */
+    protected class SimpleCallbackHandler implements CallbackHandler {
+
+        /** Name of the user. */
+        private String uname;
+
+        /** User's password. */
+        private String pass;
+
+        /**
+         * Constructor.
+         * 
+         * @param username The username
+         * @param password The password
+         */
+        public SimpleCallbackHandler(String username, String password) {
+            uname = username;
+            pass = password;
+        }
 
-               } catch (LoginException ex) {
-                       log
-                                       .error(
-                                                       "UsernamePasswordAuthenticationServlet: Error authenticating user.",
-                                                       ex);
-                       idpLoginCtx.setPrincipalAuthenticated(false);
-               }
-       }
-}
+        /**
+         * Handle a callback.
+         * 
+         * @param callbacks The list of callbacks to process.
+         * 
+         * @throws UnsupportedCallbackException If callbacks has a callback other than {@link NameCallback} or
+         *             {@link PasswordCallback}.
+         */
+        public void handle(final Callback[] callbacks) throws UnsupportedCallbackException {
+
+            if (callbacks == null || callbacks.length == 0) {
+                return;
+            }
+
+            for (Callback cb : callbacks) {
+                if (cb instanceof NameCallback) {
+                    NameCallback ncb = (NameCallback) cb;
+                    ncb.setName(uname);
+                } else if (cb instanceof PasswordCallback) {
+                    PasswordCallback pcb = (PasswordCallback) cb;
+                    pcb.setPassword(pass.toCharArray());
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/edu/internet2/middleware/shibboleth/idp/config/profile/authn/UsernamePasswordAuthenticationHandlerBeanDefinitionParser.java b/src/edu/internet2/middleware/shibboleth/idp/config/profile/authn/UsernamePasswordAuthenticationHandlerBeanDefinitionParser.java
new file mode 100644 (file)
index 0000000..e05e763
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package edu.internet2.middleware.shibboleth.idp.config.profile.authn;
+
+import javax.xml.namespace.QName;
+
+import org.apache.log4j.Logger;
+import org.opensaml.log.Level;
+import org.opensaml.xml.util.DatatypeHelper;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.w3c.dom.Element;
+
+import edu.internet2.middleware.shibboleth.idp.config.profile.ProfileHandlerNamespaceHandler;
+
+/**
+ * Spring bean definition parser for username/password authentication handlers.
+ */
+public class UsernamePasswordAuthenticationHandlerBeanDefinitionParser extends
+        AbstractAuthenticationHandlerBeanDefinitionParser {
+
+    /** Schema type. */
+    public static final QName SCHEMA_TYPE = new QName(ProfileHandlerNamespaceHandler.NAMESPACE, "UsernamePassword");
+    
+    /** Class logger. */
+    private final Logger log = Logger.getLogger("UsernamePasswordAuthenticationHandlerBeanDefinitionParser");
+
+    /** {@inheritDoc} */
+    protected Class getBeanClass(Element element) {
+        return UsernamePasswordAuthenticationHandlerFactoryBean.class;
+    }
+
+    /** {@inheritDoc} */
+    protected void doParse(Element config, BeanDefinitionBuilder builder) {
+        super.doParse(config, builder);
+
+        builder.addPropertyValue("authenticationServletURL", DatatypeHelper.safeTrim(config.getAttributeNS(null,
+                "authenticationServletURL")));
+
+        String jaasConfigurationURL = DatatypeHelper.safeTrim(config.getAttributeNS(null, "jaasConfigurationLocation"));
+        log.log(Level.CRITICAL, "Setting JAAS configuration file to: " + jaasConfigurationURL);
+        System.setProperty("java.security.auth.login.config", jaasConfigurationURL);
+    }
+}
\ No newline at end of file
diff --git a/src/edu/internet2/middleware/shibboleth/idp/config/profile/authn/UsernamePasswordAuthenticationHandlerFactoryBean.java b/src/edu/internet2/middleware/shibboleth/idp/config/profile/authn/UsernamePasswordAuthenticationHandlerFactoryBean.java
new file mode 100644 (file)
index 0000000..5b00eeb
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package edu.internet2.middleware.shibboleth.idp.config.profile.authn;
+
+import edu.internet2.middleware.shibboleth.idp.authn.provider.UsernamePasswordAuthenticationHandler;
+
+/**
+ * Factory bean for {@link UsernamePasswordAuthenticationHandler}s.
+ */
+public class UsernamePasswordAuthenticationHandlerFactoryBean extends AbstractAuthenticationHandlerFactoryBean {
+
+    /** URL to authentication servlet. */
+    private String authenticationServletURL;
+
+    /**
+     * Gets the URL to authentication servlet.
+     * 
+     * @return URL to authentication servlet
+     */
+    public String getAuthenticationServletURL() {
+        return authenticationServletURL;
+    }
+
+    /**
+     * Sets URL to authentication servlet.
+     * 
+     * @param url URL to authentication servlet
+     */
+    public void setAuthenticationServletURL(String url) {
+        authenticationServletURL = url;
+    }
+
+    /** {@inheritDoc} */
+    protected Object createInstance() throws Exception {
+        UsernamePasswordAuthenticationHandler handler = new UsernamePasswordAuthenticationHandler(
+                authenticationServletURL);
+
+        populateHandler(handler);
+
+        return handler;
+    }
+
+    /** {@inheritDoc} */
+    public Class getObjectType() {
+        return UsernamePasswordAuthenticationHandler.class;
+    }
+}
\ No newline at end of file