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

如何在JAX-RS REST API中设置上下文变量并跨类访问?

解决ServletContext变量传递问题的方案

你的核心问题是在AuthenticatedEncryptor初始化时没法直接访问ServletContext,导致无法把密钥存入上下文供GetData接口读取。下面给你一套可行的调整方案,全程基于你现有代码结构修改:

步骤1:重构AuthenticatedEncryptor,让密钥可外部传入

首先把原来硬编码在构造函数里的密钥抽出来,改成通过构造参数传入,这样后续可以灵活从配置或上下文注入:

public class AuthenticatedEncryptor {

    private static final String ALGORITHM_AES256 = "AES/GCM/NoPadding";
    private final SecretKeySpec secretKeySpec;
    private final Cipher cipher;

    // 替换原来的无参构造,改为接收密钥参数
    public AuthenticatedEncryptor(String secretKey) throws AuthenticatedEncryptionException {
        try {
            // 把十六进制密钥转成字节数组(如果你的密钥是十六进制格式)
            byte[] keyBytes = hexStringToByteArray(secretKey);
            this.secretKeySpec = new SecretKeySpec(keyBytes, "AES");
            this.cipher = Cipher.getInstance(ALGORITHM_AES256);
            // 保留你原来的其他初始化逻辑
        } catch (Exception e) {
            throw new AuthenticatedEncryptionException(e);
        }
    }

    // 新增工具方法:十六进制字符串转字节数组
    private byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i+1), 16));
        }
        return data;
    }

    // 保留你原来的encrypt、decrypt和其他工具方法
}

步骤2:用ServletContextListener在应用启动时初始化加密器并存入上下文

Web应用启动时,ServletContextListener会被触发,这时候我们可以初始化AuthenticatedEncryptor,并把密钥和加密器都存入ServletContext,确保后续所有组件都能访问到:

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener // 用注解自动注册监听器,Tomcat7+支持
public class AppStartupListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        ServletContext context = sce.getServletContext();
        
        // 这里可以改成从web.xml的context-param读取密钥(更安全易维护)
        String secretKey = "9B7D2C34A366BF890C730641E6CECF6F";
        
        try {
            // 初始化加密器
            AuthenticatedEncryptor encryptor = new AuthenticatedEncryptor(secretKey);
            // 把密钥和加密器存入上下文
            context.setAttribute("skey", secretKey);
            context.setAttribute("encryptor", encryptor);
        } catch (AuthenticatedEncryptionException e) {
            throw new RuntimeException("加密组件初始化失败", e);
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // 若有需要,这里可以清理加密器资源
    }
}

如果你的项目不支持注解扫描,就在web.xml里手动注册监听器:

<listener>
    <listener-class>com.your.package.AppStartupListener</listener-class>
</listener>

步骤3:修改EncryptedDataSourceFactory,从上下文获取加密器

原来的EncryptedDataSourceFactory自己实例化AuthenticatedEncryptor,现在改成从ServletContext读取已初始化好的加密器:

import javax.naming.NamingException;
import javax.servlet.ServletContext;
import org.apache.tomcat.jdbc.pool.DataSourceFactory;

public class EncryptedDataSourceFactory extends DataSourceFactory {

    private AuthenticatedEncryptor encryptor;

    @Override
    public DataSource createDataSource(Properties properties, Context jndiContext, boolean XA) throws Exception {
        // 从JNDI获取ServletContext(Tomcat环境下的标准方式)
        ServletContext servletContext = getServletContext(jndiContext);
        // 从上下文取出加密器
        this.encryptor = (AuthenticatedEncryptor) servletContext.getAttribute("encryptor");
        
        if (encryptor == null) {
            throw new IllegalStateException("加密器未在ServletContext中初始化");
        }

        // 保留你原来的密码解密逻辑
        PoolConfiguration poolProperties = parsePoolProperties(properties);
        poolProperties.setPassword(encryptor.decrypt(poolProperties.getPassword()));

        // 保留你原来的数据源创建逻辑
        if(poolProperties.getDataSourceJNDI() != null && poolProperties.getDataSource() == null) {
            performJNDILookup(jndiContext, poolProperties);
        }
        org.apache.tomcat.jdbc.pool.DataSource dataSource = XA ? new XADataSource(poolProperties)
                : new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
        dataSource.createPool();

        return dataSource;
    }

    // 工具方法:从JNDI Context获取ServletContext
    private ServletContext getServletContext(Context jndiContext) throws NamingException {
        return (ServletContext) jndiContext.lookup("java:comp/env/servletContext");
    }
}

步骤4:验证GetData接口的密钥读取

现在你的GetData类里的context.getAttribute("skey")就能正常拿到密钥了,不需要做任何修改。当用户访问接口时,你可以对比URL参数skey和上下文里的skey,实现权限控制。

额外优化建议

为了更安全和灵活,建议把密钥从代码中抽离,放到web.xml的上下文参数里:

<context-param>
    <param-name>encryption.secret.key</param-name>
    <param-value>9B7D2C34A366BF890C730641E6CECF6F</param-value>
</context-param>

然后在AppStartupListener里读取:

String secretKey = context.getInitParameter("encryption.secret.key");

这样后续修改密钥不需要重新编译代码,直接修改配置文件即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:26:01