Tomcat部署WAR文件时Java Jsch会话遭Linux权限拒绝的原因
本地测试(直接运行Java类)和服务器手动执行java RunClass时,JSch能正常建立会话执行shell脚本;但打包成WAR部署到同一服务器的Tomcat 7.0.76后,调用接口触发session.connect()或channelExec.connect()时抛出:
java.net.SocketException : Permission Denied, Connect Failed
此前使用ProcessBuilder也存在类似问题:本地可正常执行命令,Tomcat部署后提示Permission Denied, Cannot Execute command chmod(或conda命令)。
相关代码
Controller.java
@RequestMapping(value="/endpoint", method=RequestMethod.GET) public ResponseEntity<String> generateOrderTrail2(@RequestParam("id") String id){ ScriptCaller scriptCaller = new ScriptCaller(); scriptCaller.setId(id); scriptCaller.executeCommand(); if(scriptCaller.isSuccessful()){ return ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body("Successful"); } else{ return ResponseEntity.badRequest().contentType(MediaType.APPLICATION_JSON).body("Unsuccessful"); } }
ScriptCaller.java
boolean isSuccessful = false; public void executeCommand(){ String command = './dir/folder/executeFile.sh'; try{ JSch jsch = new JSch(); Session session = jsch.getSession(this.username, this.hostname, 22); Properties config = new Properties(); config.put("StrictHostKeyChecking", "no"); session.setConfig(config); session.setPassword(this.password); session.connect(); StringBuilder output = new StringBuilder(""); ChannelExec channelExec = (ChannelExec) session.openChannel("exec"); channelExec.setInputStream((InputStream)null); InputStreamReader stream = new InputStreamReader(channelExec.getInputStream()); channelExec.setInputStream((InputStream) null); channelExec.setCommand(command); channelExec.connect(); char[] buffer = new char[128]; int read; while ((read=stream.read(buffer, 0, buffer.length))>=0){ output.append(buffer, 0, read); } this.callerResponse = output.toString(); int exitCode = channelExec.getExitStatus(); channelExec.disconnect(); session.disconnect(); if(exitCode == 0){ System.out.println("Shell Script Executed Successfully"); isSuccessful = true; } else{ System.out.println("Shell Script Failed to Execute"); isSuccessful = false; } } catch(Exception e){ e.printStackTrace(); isSuccessful = false; } }
构建与异常信息
Maven构建命令:
mvn clean deploy -Denvironment=local -Dskip.webapp=true -Dmaven.test.skip=true
异常栈关键信息:
java.net.SocketException: Permission Denied(connect failed) .. .. at com.jcraft.jsch.Util.createSocket(Util.java)
已确认:shell文件权限为777,同一用户本地/手动运行Java类正常,仅Tomcat部署后触发权限拒绝。
原因分析
1. Tomcat运行用户与手动执行用户不一致
Tomcat通常以专用系统用户(如tomcat、www-data)运行,和你手动执行Java类的登录用户不是同一身份。即便脚本设为777,Tomcat用户可能被系统限制:
- 无出站网络连接权限(防火墙/安全策略禁止该用户发起SSH连接)
- 无脚本所在父目录的
x权限(无法进入目录访问脚本)
2. SELinux/AppArmor安全策略拦截
多数Linux服务器启用SELinux或AppArmor,这类安全框架会限制服务进程的操作:
- 默认禁止Tomcat进程发起SSH连接
- 限制Tomcat执行
chmod、conda等系统/第三方命令
3. Tomcat安全管理器权限限制
Tomcat 7默认可能启用安全管理器(SecurityManager),其catalina.policy配置文件未授予程序:
- 发起网络连接的权限
- 执行系统命令、访问指定文件的权限
4. 环境变量差异
手动运行Java类时使用当前用户的完整环境变量,而Tomcat启动时会重置环境变量,导致conda等命令的路径未被加载,实际是命令找不到,但错误提示被混淆为权限问题。
排查与解决建议
确认Tomcat运行用户
执行ps aux | grep tomcat查看Tomcat的运行用户,切换到该用户手动执行java RunClass,验证是否复现问题。检查SELinux/AppArmor状态
- 执行
getenforce查看SELinux是否开启,临时关闭setenforce 0测试是否恢复正常;若正常,需添加SELinux策略允许Tomcat发起SSH连接。 - 若使用AppArmor,检查Tomcat的配置文件是否限制了网络或命令执行。
- 执行
调整Tomcat安全管理器权限
若启用了安全管理器,在conf/catalina.policy中添加对应权限(替换your-war-name为实际WAR包名):grant codeBase "file:${catalina.base}/webapps/your-war-name/-" { permission java.net.SocketPermission "*:22", "connect,resolve"; permission java.io.FilePermission "/dir/folder/executeFile.sh", "execute"; };统一Tomcat环境变量
在Tomcat的bin/setenv.sh中添加conda等命令的路径:export PATH=/path/to/conda/bin:$PATH检查父目录权限
确保脚本所在的/dir/folder/父目录给Tomcat用户分配了x权限(允许进入目录),执行chmod o+x /dir/folder/测试。
内容的提问来源于stack exchange,提问作者Sarath M

