You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JASPIC ServerAuthModule中CDI注入DataSource失效问题

解决JASPIC ServerAuthModule中@Resource注入DataSource为null的问题

我之前也碰到过一模一样的问题!核心原因其实很清晰:你的SrvAuthModuleImpl是由WildFly容器直接实例化的,不属于CDI容器管理的Bean——哪怕你在beans.xml里设置了bean-discovery-mode="all",CDI也不会对这个类的实例做依赖注入处理。而同一个WAR里的Servlet是CDI管辖范围内的Bean,所以注入能正常工作,这就是两者的本质区别。

下面给你两个可行的解决方案,按需选择:

方案1:通过CDI API手动获取资源(简单直接)

既然认证模块不在CDI的管理范围内,我们可以主动获取CDI上下文,从中拿到需要的DataSource或者其他全局CDI Bean。

首先确保你的项目引入了CDI API依赖(WildFly自带这个API,所以只需要在pom.xml里添加provided依赖即可,不用打包到WAR里):

<dependency>
    <groupId>javax.enterprise</groupId>
    <artifactId>cdi-api</artifactId>
    <version>2.0</version>
    <scope>provided</scope>
</dependency>

然后修改你的认证模块代码,添加一个获取CDI资源的方法,这里有两种更实用的方式:

方式A:通过CDI持有Bean间接获取(更灵活)

如果你需要管理更多全局资源,建议先把DataSource注入到一个CDI管理的Bean里,再通过CDI获取这个Bean:

// 先创建一个ApplicationScoped的持有Bean,用来托管DataSource
@ApplicationScoped
public class DBResourceHolder {
    @Resource(mappedName="java:jboss/datasources/someDB")
    private DataSource db;

    public DataSource getDb() {
        return db;
    }
}

接着在认证模块里获取这个Bean:

import javax.enterprise.inject.spi.CDI;

// ... 其他原有代码 ...

private DataSource getDataSourceViaCDI() {
    try {
        return CDI.current().select(DBResourceHolder.class).get().getDb();
    } catch (Exception e) {
        throw new AuthException("Failed to retrieve DataSource via CDI holder", e);
    }
}

最后在validateRequest方法里调用这个新方法代替原来的getDb():

@Override
public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException {
    try (Connection c = getDataSourceViaCDI().getConnection()) {
        // 你的认证逻辑代码
    } catch (SQLException ex) {
        // 异常处理逻辑
    }
    return AuthStatus.SUCCESS;
}

方式B:直接通过CDI获取DataSource

如果你的DataSource可以通过CDI限定符(比如@Named)匹配,也可以直接查找:

import javax.enterprise.inject.spi.CDI;
import javax.inject.Named;

// ... 其他原有代码 ...

private DataSource getDataSourceViaCDI() {
    try {
        // 用@Named限定符匹配数据源,名称要和配置对应
        return CDI.current().select(DataSource.class, new NamedLiteral("someDB")).get();
    } catch (Exception e) {
        throw new AuthException("Failed to retrieve DataSource via CDI", e);
    }
}

// 实现NamedLiteral类,因为CDI的@Named是注解,不能直接传入
class NamedLiteral extends AnnotationLiteral<Named> implements Named {
    private final String value;

    public NamedLiteral(String value) {
        this.value = value;
    }

    @Override
    public String value() {
        return value;
    }
}

方案2:让ServerAuthModule成为CDI管理的Bean(进阶)

如果你希望整个认证模块都由CDI管理(比如需要注入多个CDI Bean),可以通过自定义AuthConfigProvider来实现:

  1. 给SrvAuthModuleImpl添加CDI作用域注解,比如@ApplicationScoped:
@ApplicationScoped
public class SrvAuthModuleImpl implements ServerAuthModule {
    @Resource(mappedName="java:jboss/datasources/someDB")
    private DataSource db;

    // ... 其他原有代码 ...
}
  1. 实现自定义的AuthConfigProvider,在里面通过CDI获取ServerAuthModule的实例,代替容器直接创建:
public class CDIAuthConfigProvider implements AuthConfigProvider {
    @Override
    public AuthConfig getAuthConfig(String layer, String appContext, RegistrationListener listener) throws AuthException {
        return new AuthConfig() {
            @Override
            public ServerAuthConfig getServerAuthConfig(String layer, String appContext, RegistrationListener listener) throws AuthException {
                return new ServerAuthConfig() {
                    // 重点实现getAuthContext方法,通过CDI获取模块实例
                    @Override
                    public AuthContext getAuthContext(String authContextID, Subject serviceSubject, Map properties) throws AuthException {
                        ServerAuthModule module = CDI.current().select(SrvAuthModuleImpl.class).get();
                        module.initialize(null, null, handler, properties);
                        return new AuthContext() {
                            @Override
                            public AuthStatus validateRequest(MessageInfo messageInfo, Subject clientSubject, Subject serviceSubject) throws AuthException {
                                return module.validateRequest(messageInfo, clientSubject, serviceSubject);
                            }

                            // 实现其他AuthContext方法,直接调用module对应的方法即可
                            @Override
                            public AuthStatus secureResponse(MessageInfo messageInfo, Subject serviceSubject) throws AuthException {
                                return module.secureResponse(messageInfo, serviceSubject);
                            }

                            @Override
                            public void cleanSubject(MessageInfo messageInfo, Subject subject) throws AuthException {
                                module.cleanSubject(messageInfo, subject);
                            }
                        };
                    }

                    // 实现其他ServerAuthConfig必要方法...
                    @Override
                    public String getMessageLayer() {
                        return "HttpServlet";
                    }

                    @Override
                    public String getAppContext() {
                        return "";
                    }

                    // 其他方法按需实现
                };
            }

            // 实现其他AuthConfig必要方法...
        };
    }

    // 实现其他AuthConfigProvider必要方法...
}
  1. 在项目的META-INF/services/javax.security.auth.message.config.AuthConfigProvider文件里注册你的自定义Provider:
com.yourpackage.CDIAuthConfigProvider

这个方案相对复杂,但能让整个认证模块享受到CDI的依赖注入能力,适合需要注入多个CDI资源的场景。

补充说明

你之前用InitialContext手动查找的临时方案是完全可行的,因为JNDI查找不受CDI管理的限制,只要数据源的JNDI名称正确就能拿到。如果只是需要DataSource,方案1的方式B已经足够简单好用。

内容的提问来源于stack exchange,提问作者hrs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:07:30