Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wildfly custom login modules error

Tags:

wildfly

jaas

I implemented a custom loginModule that is accessed via a web service and checks a username and password infront of a JPA accessed DB. I ran it on jboss 7.1 and it worked fine, but after moving it to Wildfly (and adding what i think is the right configuration) i get a NullPointerException stemming from way inside the wildfly classes. any ideas?

18:48:21,417 ERROR [io.undertow.request] (default task-3) UT005023: Exception handling request to /jass.ws/jaas/verifier/authenticateWithBasicUsernamePasswordAuth: java.lang.RuntimeException: java.lang.NullPointerException
    at org.wildfly.extension.undertow.security.JAASIdentityManagerImpl.verifyCredential(JAASIdentityManagerImpl.java:126)
    at org.wildfly.extension.undertow.security.JAASIdentityManagerImpl.verify(JAASIdentityManagerImpl.java:82)
    at io.undertow.security.impl.BasicAuthenticationMechanism.authenticate(BasicAuthenticationMechanism.java:110) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.security.impl.SecurityContextImpl$AuthAttempter.transition(SecurityContextImpl.java:281) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.security.impl.SecurityContextImpl$AuthAttempter.transition(SecurityContextImpl.java:298) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.security.impl.SecurityContextImpl$AuthAttempter.access$100(SecurityContextImpl.java:268) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.security.impl.SecurityContextImpl.attemptAuthentication(SecurityContextImpl.java:131) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.security.impl.SecurityContextImpl.authTransition(SecurityContextImpl.java:106) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.security.impl.SecurityContextImpl.authenticate(SecurityContextImpl.java:99) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.security.handlers.AuthenticationCallHandler.handleRequest(AuthenticationCallHandler.java:50) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.security.handlers.AuthenticationConstraintHandler.handleRequest(AuthenticationConstraintHandler.java:51) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:45) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:61) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.servlet.handlers.security.ServletSecurityConstraintHandler.handleRequest(ServletSecurityConstraintHandler.java:56) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:58) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:70) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.security.handlers.SecurityInitialHandler.handleRequest(SecurityInitialHandler.java:76) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:25) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:240) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:227) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:73) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:146) [undertow-servlet-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:168) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:687) [undertow-core-1.0.0.Final.jar:1.0.0.Final]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [rt.jar:1.7.0_51]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [rt.jar:1.7.0_51]
    at java.lang.Thread.run(Thread.java:744) [rt.jar:1.7.0_51]
Caused by: java.lang.NullPointerException
    at org.wildfly.extension.undertow.security.AccountImpl.<init>(AccountImpl.java:61)
    at org.wildfly.extension.undertow.security.JAASIdentityManagerImpl.verifyCredential(JAASIdentityManagerImpl.java:123)
... 29 more

This is my custom login module:

public class JPALoginModule implements LoginModule {

    CallbackHandler callbackHandler;
    Subject subject;
    Map sharedState;
    Map options;

    boolean success;

    LoginVerifier loginVerifier;

    @Override
    public void initialize(Subject subject, CallbackHandler callbackHandler,
        Map<String, ?> sharedState, Map<String, ?> options) {
        System.out.println("JPALoginModule.initialize()");
        this.callbackHandler = callbackHandler;
        this.subject = subject;
        this.sharedState = sharedState;
        this.options = options;
        InitialContext context = null;
        try {
            context = new InitialContext();
        } catch (NamingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        try {
            this.loginVerifier = (LoginVerifier) context.lookup("java:global/jaas.ear/jaas.ejb/LoginVerifierBean!beans.LoginVerifier");
        } catch (NamingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    public boolean login() throws LoginException {
        System.out.println("JPALoginModule.login()");
        try {
            // Setup default callback handlers.
            Callback[] callbacks = new Callback[] {
                new NameCallback("Username: "),
                new PasswordCallback("Password: ", false) };

            callbackHandler.handle(callbacks);

            String username = ((NameCallback) callbacks[0]).getName();
            String password = new String(
                ((PasswordCallback) callbacks[1]).getPassword());

            success = loginVerifier.verify(username, password);

            if (!success) {
                throw new LoginException(
                    "Authentication Failed: Wrong Password");
            } else if (success) {
                return true;
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (UnsupportedCallbackException e) {
            e.printStackTrace();
        }
        catch (NullPointerException e) {
            System.out.println(e.getMessage()+" "+e.getLocalizedMessage());
        }
        return false;
    }

    @Override
    public boolean commit() throws LoginException {
        if (success) {
            if (subject.isReadOnly()) {
                throw new LoginException("subject is read only");
            }
            if (callbackHandler instanceof PassiveCallBackHandler) {
                ((PassiveCallBackHandler) callbackHandler).clearPassword();
            }
            return true;
        }
        else {
            return true;
        }
    }

    @Override
    public boolean abort() throws LoginException {
        logout();
        return true;
    }

    @Override
    public boolean logout() throws LoginException {
        if (callbackHandler instanceof PassiveCallBackHandler) {
            ((PassiveCallBackHandler) callbackHandler).clearPassword();
        }
        return true;
    }

my jboss-web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
    <security-domain>java:/jaas/jpa-login-module</security-domain>
</jboss-web>

my web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>jass.ws</display-name>
<security-constraint>
    <web-resource-collection>
        <web-resource-name></web-resource-name>
        <url-pattern>/*</url-pattern>
        <http-method>GET</http-method>
        <http-method>POST</http-method>
    </web-resource-collection>
</security-constraint>
<login-config>
    <auth-method>BASIC</auth-method>
    <realm-name>jpa-login-module</realm-name>
</login-config>

and my standalone.xml relevant configuration:

<security-domains>
     <security-domain name="jpa-login-module" cache-type="default">
         <authentication>
             <login-module code="com.jaas.JPALoginModule" flag="required"/>
         </authentication>
     </security-domain>
     <security-domain name="jpa-password-username" cache-type="default">
         <authentication>
             <login-module code="com.jaas.JPAUsernamePasswordLoginModule" flag="required"/>
         </authentication>
     </security-domain>
     <security-domain name="other" cache-type="default">
         <authentication>
             <login-module code="Remoting" flag="optional">
                 <module-option name="password-stacking" value="useFirstPass"/>
             </login-module>
             <login-module code="RealmDirect" flag="required">
                 <module-option name="password-stacking" value="useFirstPass"/>
             </login-module>
         </authentication>
     </security-domain>
     <security-domain name="jboss-web-policy" cache-type="default">
         <authorization>
             <policy-module code="Delegating" flag="required"/>
         </authorization>
     </security-domain>
     <security-domain name="jboss-ejb-policy" cache-type="default">
         <authorization>
             <policy-module code="Delegating" flag="required"/>
         </authorization>
     </security-domain>
</security-domains>
like image 305
ido flax Avatar asked May 23 '14 17:05

ido flax


2 Answers

Ok I figured out the reason, it's because I didn't add principals after authentication.

So I added this:

if (!success) {
    throw new LoginException(
        "Authentication Failed: Wrong Password");
} else if (success) {
    Principal passPrincipal = new UsernamePrincpal(username);
    subject.getPrincipals().add(passPrincipal);
    subject.getPrivateCredentials().add(password);
    return true;
}

and it works

like image 117
ido flax Avatar answered Nov 10 '22 20:11

ido flax


The NPE is definitely a bug in the WildFly's Undertow extension. I've created a new JIRA - WFLY-3416.

Nevertheless the safe way to write custom login modules for JBoss AS and Wildfly is to subclass org.jboss.security.auth.spi.AbstractServerLoginModule.

Try something like:

import java.security.Principal;
import java.security.acl.Group;
import java.util.Map;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginException;

import org.jboss.security.auth.spi.AbstractServerLoginModule;

public class JPALoginModule extends AbstractServerLoginModule {

    LoginVerifier loginVerifier;
    java.security.Principal identity;

    @Override
    public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
        System.out.println("JPALoginModule.initialize()");
        super.initialize(subject, callbackHandler, sharedState, options);
        InitialContext context = null;
        try {
            context = new InitialContext();
        } catch (NamingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        try {
            this.loginVerifier = (LoginVerifier) context
                    .lookup("java:global/jaas.ear/jaas.ejb/LoginVerifierBean!beans.LoginVerifier");
        } catch (NamingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    public boolean login() throws LoginException {
        System.out.println("JPALoginModule.login()");
        try {
            // Setup default callback handlers.
            Callback[] callbacks = new Callback[] { new NameCallback("Username: "), new PasswordCallback("Password: ", false) };

            callbackHandler.handle(callbacks);

            String username = ((NameCallback) callbacks[0]).getName();
            String password = new String(((PasswordCallback) callbacks[1]).getPassword());

            if (!loginVerifier.verify(username, password)) {
                throw new LoginException("Authentication Failed: Wrong Password");
            }
            try {
                identity = createIdentity(username);
            } catch (Exception e) {
                throw new LoginException("Unable to Create Identity");
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        } catch (UnsupportedCallbackException e) {
            e.printStackTrace();
        } catch (NullPointerException e) {
            System.out.println(e.getMessage()+" "+e.getLocalizedMessage());
        }
        return false;
    }

    @Override
    public boolean commit() throws LoginException {
        if (identity != null) {
            if (subject.isReadOnly()) {
                throw new LoginException("subject is read only");
            }
            if (callbackHandler instanceof PassiveCallBackHandler) {
                ((PassiveCallBackHandler) callbackHandler).clearPassword();
            }
        }
        return super.commit();
    }

    @Override
    public boolean abort() throws LoginException {
        logout();
        return true;
    }

    @Override
    public boolean logout() throws LoginException {
        if (callbackHandler instanceof PassiveCallBackHandler) {
            ((PassiveCallBackHandler) callbackHandler).clearPassword();
        }
        return super.logout();
    }

    @Override
    protected Principal getIdentity() {
        return identity;
    }

    @Override
    protected Group[] getRoleSets() throws LoginException {
        return new Group[0];
    }
}

This will prevent the NullPointerException, because Principal instance is created (in identity member variable) when user is successfully authenticated.

like image 33
kwart Avatar answered Nov 10 '22 21:11

kwart