WildFly 26.1.3中EJB客户端切换服务器二次调用认证失败问题
动态切换WildFly EJB服务器时二次调用出现认证错误问题排查
问题现象
在WildFly 26.1.3环境下,实现EJB客户端动态切换两台WildFly EJB服务器时出现异常,具体流程:
- 首次调用Server 1成功(创建新Context并完成JNDI查找)
- 首次调用Server 2成功
- 二次调用Server 2时触发认证错误
需求为根据用户请求调用不同服务器的EJB服务。
测试代码
import javax.naming.Context; import javax.naming.InitialContext; import java.util.Hashtable; public class EjbClient { private static Context getContext(String serverUrl, String username, String password) { Hashtable<String, String> env = new Hashtable<>(); env.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory"); env.put(Context.PROVIDER_URL, serverUrl); env.put(Context.SECURITY_PRINCIPAL, username); env.put(Context.SECURITY_CREDENTIALS, password); env.put("jboss.naming.client.ejb.context", "true"); try { return new InitialContext(env); } catch (Exception e) { throw new RuntimeException("Failed to create context", e); } } public static void callEjb(String serverUrl, String username, String password) { try (Context ctx = getContext(serverUrl, username, password)) { // 示例EJB接口与JNDI名称,根据实际场景调整 HelloRemote ejb = (HelloRemote) ctx.lookup("ejb:/ejb-server/HelloBean!" + HelloRemote.class.getName()); String result = ejb.sayHello(); System.out.println("调用结果: " + result); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { String server1Url = "http-remoting://server1:8080"; String server2Url = "http-remoting://server2:8080"; // 首次调用Server1 callEjb(server1Url, "user1", "pass1"); // 首次调用Server2 callEjb(server2Url, "user2", "pass2"); // 二次调用Server2,触发认证错误 callEjb(server2Url, "user2", "pass2"); } }
报错输出
javax.naming.AuthenticationException: Failed to connect to any server. Servers tried: [http-remoting://server2:8080 (Authentication failed: all available authentication mechanisms failed: DIGEST-MD5: Server rejected authentication)] at org.wildfly.naming.client.remote.RemoteNamingStore.failOverOrThrow(RemoteNamingStore.java:243) at org.wildfly.naming.client.remote.RemoteNamingStore.connect(RemoteNamingStore.java:163) at org.wildfly.naming.client.remote.RemoteNamingStore.lookup(RemoteNamingStore.java:267) at org.wildfly.naming.client.remote.RemoteContext.lookup(RemoteContext.java:108) at org.wildfly.naming.client.remote.RemoteContext.lookup(RemoteContext.java:126) at javax.naming.InitialContext.lookup(InitialContext.java:417) at com.example.EjbClient.callEjb(EjbClient.java:22) at com.example.EjbClient.main(EjbClient.java:38) Caused by: javax.security.sasl.SaslException: Authentication failed: all available authentication mechanisms failed: DIGEST-MD5: Server rejected authentication at org.wildfly.security.sasl.util.AbstractSaslClient.evaluateChallenge(AbstractSaslClient.java:214) at org.wildfly.naming.client.remote.RemoteNamingStore.lambda$connect$0(RemoteNamingStore.java:136) at org.wildfly.naming.client.remote.RemoteNamingStore.retryConnect(RemoteNamingStore.java:204) at org.wildfly.naming.client.remote.RemoteNamingStore.connect(RemoteNamingStore.java:132) ... 6 more
问题原因及解决方案
原因分析
WildFly EJB客户端默认会缓存连接与认证上下文,切换服务器后二次调用时,可能复用了前一台服务器的认证凭证,导致目标服务器认证失败;此外InitialContext资源未彻底释放,也会导致连接池中的旧连接被错误复用。
解决办法
禁用连接复用
创建InitialContext时添加环境变量,强制每次调用生成新连接,避免复用缓存的认证上下文:env.put("jboss.naming.client.connect.options.org.xnio.Options.NO_REUSE", "true");确保Context彻底释放
即便使用try-with-resources,部分场景下RemoteContext可能未完全释放连接,可手动在finally块中关闭上下文:public static void callEjb(String serverUrl, String username, String password) { Context ctx = null; try { ctx = getContext(serverUrl, username, password); HelloRemote ejb = (HelloRemote) ctx.lookup("ejb:/ejb-server/HelloBean!" + HelloRemote.class.getName()); String result = ejb.sayHello(); System.out.println("调用结果: " + result); } catch (Exception e) { e.printStackTrace(); } finally { if (ctx != null) { try { ctx.close(); } catch (Exception e) { e.printStackTrace(); } } } }使用独立客户端配置
为每台服务器创建独立的EJBClientConfiguration,避免共享上下文资源:import org.wildfly.naming.client.WildFlyInitialContextFactory; import org.wildfly.naming.client.config.EJBClientConfiguration; import org.wildfly.naming.client.config.PropertiesBasedEJBClientConfiguration; private static Context getContext(String serverUrl, String username, String password) { Hashtable<String, String> props = new Hashtable<>(); props.put("remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED", "false"); props.put("remote.connections", "default"); props.put("remote.connection.default.host", serverUrl.split("://")[1].split(":")[0]); props.put("remote.connection.default.port", serverUrl.split("://")[1].split(":")[1]); props.put("remote.connection.default.username", username); props.put("remote.connection.default.password", password); props.put("remote.connection.default.connect.options.org.xnio.Options.NO_REUSE", "true"); EJBClientConfiguration config = new PropertiesBasedEJBClientConfiguration(props); Hashtable<String, Object> env = new Hashtable<>(); env.put(WildFlyInitialContextFactory.EJB_CLIENT_CONFIGURATION, config); env.put(Context.INITIAL_CONTEXT_FACTORY, WildFlyInitialContextFactory.class.getName()); try { return new InitialContext(env); } catch (Exception e) { throw new RuntimeException("Failed to create context", e); } }检查服务器端认证配置
确认两台服务器的security-domain配置一致,比如standalone.xml中DIGEST-MD5等认证机制的参数,避免因凭证格式或认证规则不匹配导致失败。
内容的提问来源于stack exchange,提问作者Ganesh
相关产品推荐
相关产品推荐

