如何在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
相关产品推荐
相关产品推荐

