在Quarkus中结合CXF WSS4J与Elytron Security,通过扩展UsernameTokenValidator实现LDAP认证的技术咨询
在Quarkus中结合CXF WSS4J与Elytron Security,通过扩展UsernameTokenValidator实现LDAP认证的技术咨询
看起来你已经找对了核心方向——通过扩展CXF的UsernameTokenValidator对接Quarkus Elytron的LDAP认证,这是个很合理的方案,我来帮你把这个实现补全,同时梳理几个关键的注意点:
首先,先确保你的项目依赖是完整的,pom.xml里需要包含这几个核心依赖:
<dependencies> <!-- Quarkus CXF JAX-WS支持 --> <dependency> <groupId>io.quarkiverse.cxf</groupId> <artifactId>cxf-quarkus-jaxws</artifactId> </dependency> <!-- Quarkus Elytron LDAP安全扩展 --> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-elytron-security-ldap</artifactId> </dependency> <!-- CXF WS安全模块 --> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-security</artifactId> </dependency> </dependencies>
接下来是核心的UsernameTokenLDAPValidator实现,这里要注意绝对不能用new来实例化这个类,因为我们需要注入Quarkus的安全认证API来复用Elytron的LDAP配置,所以要把它声明为CDI Bean,然后在CXF Endpoint配置中注入使用:
import org.apache.cxf.ws.security.tokenstore.SecurityToken; import org.apache.cxf.ws.security.validate.UsernameTokenValidator; import org.apache.cxf.ws.security.validate.ValidationResult; import io.quarkus.security.AuthenticationRequest; import io.quarkus.security.SecurityAuthenticator; import io.quarkus.security.identity.SecurityIdentity; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import org.w3c.dom.Element; @ApplicationScoped public class UsernameTokenLDAPValidator extends UsernameTokenValidator { @Inject SecurityAuthenticator securityAuthenticator; @Override public ValidationResult validate(SecurityToken token, Element element, String realm) { // 先调用父类做基础的格式验证,比如token的结构是否合法 ValidationResult result = super.validate(token, element, realm); if (result.getStatus() == ValidationResult.Status.VALID) { String username = token.getPrincipal().getName(); // 从token中获取明文密码(你的场景是明文密码传输,所以直接取即可) String plainPassword = (String) token.getCredentials(); try { // 复用Quarkus Elytron的认证API执行LDAP验证 AuthenticationRequest authRequest = AuthenticationRequest .withUsername(username) .password(plainPassword.toCharArray()) .build(); // 同步执行认证,你也可以根据业务需求改用异步方式 SecurityIdentity authenticatedIdentity = securityAuthenticator.authenticate(authRequest) .await() .indefinitely(); // 认证成功后,把Elytron返回的身份信息绑定到CXF的验证结果中 result.setPrincipal(authenticatedIdentity.getPrincipal()); result.setCredentials(authenticatedIdentity.getCredentials()); } catch (Exception e) { // 认证失败时设置错误状态和提示信息,CXF会自动返回对应的SOAP错误 result.setStatus(ValidationResult.Status.INVALID); result.setErrorMessage("LDAP用户认证失败:" + e.getMessage()); } } return result; } }
然后补全你的CXF Endpoint配置,这里要注意注入我们的Validator(而不是直接new),同时完善WSS4J的入站拦截器配置:
import org.apache.cxf.quarkus.core.CxfEndpoint; import org.apache.cxf.ws.security.SecurityConstants; import org.apache.cxf.ws.security.wss4j.WSS4JInHandler; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Produces; import jakarta.inject.Inject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @ApplicationScoped public class CxfServiceProducer { @Inject UsernameTokenLDAPValidator usernameTokenLDAPValidator; @Produces public CxfEndpoint myServiceEndpoint() { CxfEndpoint cxfEndpoint = new CxfEndpoint(); cxfEndpoint.setDataFormat(CxfEndpoint.DataFormat.CXF_MESSAGE); cxfEndpoint.setWsdlURL("wsdl/Service123.wsdl"); cxfEndpoint.setAddress("/MyService"); // 配置CXF安全属性,指定我们的自定义UsernameToken验证器 Map<String, Object> endpointProps = new HashMap<>(); endpointProps.put(SecurityConstants.USERNAME_TOKEN_VALIDATOR, usernameTokenLDAPValidator); cxfEndpoint.setProperties(endpointProps); // 配置WSS4J入站处理器,开启UsernameToken验证逻辑 Map<String, Object> securityProps = new HashMap<>(); securityProps.put("action", "UsernameToken"); // 因为你的场景是明文密码,所以指定密码类型为PasswordText securityProps.put("passwordType", "PasswordText"); List<Map<String, Object>> inInterceptors = new ArrayList<>(); Map<String, Object> wss4jInHandlerProps = new HashMap<>(securityProps); wss4jInHandlerProps.put("handlerClass", WSS4JInHandler.class.getName()); inInterceptors.add(wss4jInHandlerProps); cxfEndpoint.setInInterceptors(inInterceptors); return cxfEndpoint; } }
最后,在application.properties中配置Elytron的LDAP参数,这里以标准的开源LDAP服务器为例:
# 启用Elytron LDAP安全模块 quarkus.elytron.security.ldap.enabled=true # LDAP服务器地址,生产环境建议用LDAPS(ldaps://xxx:636) quarkus.elytron.security.ldap.url=ldap://your-ldap-host:389 # 用户搜索的基础DN,根据你的LDAP目录结构调整 quarkus.elytron.security.ldap.search-base-dn=ou=users,dc=your-domain,dc=com # 用户搜索过滤规则,{0}会被自动替换为待验证的用户名 quarkus.elytron.security.ldap.search-filter=(uid={0}) # LDAP管理员账号(用于绑定LDAP服务器并搜索用户) quarkus.elytron.security.ldap.context-source.principal=cn=admin,dc=your-domain,dc=com # LDAP管理员密码 quarkus.elytron.security.ldap.context-source.credential=your-admin-password # 用户的用户名属性,对应LDAP中的用户唯一标识字段 quarkus.elytron.security.ldap.principal-name-attribute=uid
几个关键的注意点:
- 绝对不要手动编写LDAP连接和验证代码,完全复用Quarkus Elytron的实现,这样可以获得Quarkus的配置管理、连接池、错误处理等原生能力
- 你的场景是明文密码传输,生产环境必须强制使用HTTPS,否则密码会在网络中明文泄露
- 如果你的LDAP服务器开启了TLS加密,只需要把url改成
ldaps://your-ldap-host:636,并根据需要配置信任库参数
备注:内容来源于stack exchange,提问作者coder5749
相关产品推荐
相关产品推荐

