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>
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With