JBoss 项目修复笔记:绕开 iframe 安全问题,JSF 与 Angular 最小代价共存方案
本篇笔记衔接的内容为:JBoss + WildFly 本地开发环境完全指南,里面简单的描述了一下怎么配置 docker,在本地启动一个可以运行的 JBoss 和 WildFly 服务器,接下来就简单描述一下想要解决的问题,以及目前看来比较可靠的解决方法
前言- 背景介绍 & 目标
简单的 recap 一下,我们现在的问题是:
- 前端还在使用 JSF
毕竟这是一个老项目了,还在 active support 这个项目的那段时光里,JSF 毕竟还是主流 - 前端尝试使用 AngularJS
是 AngularJS,不是 Angular;是 1.x 的版本,不是 2.0+。当时的开发大概是感受到了 JSF 混合 xhtml 的问题——冗长、结构特别复杂、沉重并难以增添新的功能。再搭配上 AngularJS 的确是有 Google 背书,也出现了不少社区支持的 packages,比如说 ng-grid,ng-table 之类,实现起来比春写 JSF 要容易不少
目前来说我并没有打算深挖 AngularJS 的打算,毕竟我开始做的时候就做 SPA 了,虽然简单的碰过一点 Angular 的内容,也写了点笔记,不过毕竟不是主营 - 使用 iframe 导入 AngularJS 实现的页面
这里主要的问题在于,主 xhtml 和使用 iframe 导入的 AngularJS 页面并不分享一个共同的 context,session id 需要通过 URL 明文传到 AngularJS 页面中,最终也是导致了 InfoSec team 给我们好多个 tickets,不能搞定这些安全隐患,那么也就影响现在的生产环境
当然,这里也有其他的问题,比如说导入非常的散乱,控制器(angular controller)也散的到处都是。不过因为业务相对简单,这种问题还是可以解决的,而且也不是 red flag 🚩,暂时睁一只眼闭一只眼就可以了
鉴于当前的项目也快进入 EOL 了 这种项目提了多少年 EOL 了,什么时候真的 EOL 也不知道,因此当前的目标就是:
- 最小规模的修改代码,即不动原有的框架结构、文件路径等
这也可以保证不需要动其他的 xml 文件和 pom 文件,复用原本的 build process - 顺利移除 iframe
这里最大的问题在于 iframe 和主 xhtml 不分享一个 context,那么,如果可以把原本 iframe 运行的内容,放到 xhtml 中,原本的 session 就可以共享,剩下的生命周期流程也可以交给 java 去管理 - 直接在 xhtml 文件里运行 angular
这里其实有蛮多的问题的
项目复刻
首先看一下现在的结构:
这里主要修改的就是 webapp/webapp
下面的内容, app
中的是 angular.html
运行的 controller, lib/angular
下面是 angular 官方的 js 和 css 文档, index.xhtml
是 entry point, original.xhtml
是使用 iframe 的 copy,大体结构如此
具体的代码如下:
-
main.controller.js
这里的代码比较短,主要内容如下:(function () {"use strict";angular.module("demoApp", []).controller("MainController", ["$scope",function ($scope) {$scope.title = "🚀 AngularJS 1.3.17 Demo Page";$scope.userInput = "";$scope.items = ["banana", "apple", "mongo"];$scope.addItem = function () {$scope.items.push("new item " + ($scope.items.length + 1));};},]); })();
这个写过 Angular 的人多多少少会有点眼熟,Angular 的实现——尽管内部完全不同,但是从实用的角度上来说,还是比较相似的:
module
就是新建一个 module,后面的 array 与依赖有关,大体对标的是NgModule
controller
对标的大体就是 controller 中的内容,里面的实现大体对标的事@Component
中的实现$scope
中可以绑定的就是各种的变量和方法
整体上可以看出来,AngularJS → Angular 虽然实现方法是完全推翻了,但是核心的实现概念还是一致的。2+比起 1 来说更加的类型安全——感谢 TypeScript 带来的安全感,使用起来也更加的灵活,毕竟:- 2+使用的是 TS,而且在编译时进行变量和方法的校验;1 则是使用 JS,运行时动态绑定
- 2+使用的是 class+decorator 的方法;1 使用的是 controller+scope 的方法
- 2+默认的是单向绑定,双向绑定需要使用
[(ngModel)]
;1 默认开启双向绑定,没有什么特别好的选择 - 2+模板需要 controller 和 view 进行 MVVM 的交流;1 基本在 HTML 中写 template
我个人感觉,小范围内的项目,1 的写法可能会更方便一些,但是一代码量比较大,或者是陌生的代码,那么 1 找对应的逻辑就比较头疼
2+的写法虽然相对而言更麻烦一点,不过基于 TS 的检查,以及现代编辑器/IDE 对于 Angular 项目的良好支持,通过查找使用的 reference,和直接点击变量,在 controller 和 view 层来回切换,查找逻辑反而没有那么的困难
-
lib
这里的内容可以从官方文档里找:https://code.angularjs.org/
按需下载即可 -
angular.html
一个简单的 demo,作为 iframe 的导入对象使用,代码如下:<!DOCTYPE html> <html lang="en" ng-app="demoApp"><head><meta charset="UTF-8" /><title>AngularJS 1.3 Demo</title><link rel="stylesheet" href="lib/angular/angular-csp.css" /><script src="lib/angular/angular.js"></script><script src="app/main.controller.js"></script></head><body ng-controller="MainController"><div style="padding: 2em; font-family: sans-serif;"><h1>{{ title }}</h1><p>🔁 double binding Test:</p><inputtype="text"ng-model="userInput"placeholder="Enter something here..."/><p>You have entered: <strong>{{ userInput }}</strong></p><hr /><p>📋 ng-repeat list rendering:</p><ul><li ng-repeat="item in items track by $index">{{ $index + 1 }}. {{ item }}</li></ul><button ng-click="addItem()">Add more item</button><hr /><p>🎯 ng-if:</p><p ng-if="items.length > 3" style="color: green;">Contrags, you have added more than 3 items!</p></div></body> </html>
本质上就是一个比较简单的逻辑,用来确认 AngularJS 的双向绑定、方法、参数等都能在 View 和 Controller 中来回正常交流
-
original.xhtml
这里就是比较暴力的检验方法了:<!DOCTYPE html> <htmlxmlns="http://www.w3.org/1999/xhtml"xmlns:h="http://xmlns.jcp.org/jsf/html" ><h:head><title>Mock JSF App</title></h:head><h:body><h1>Hello from JSF!</h1><iframesrc="angular/angular.html"style="width: 100%; height: 80vh; border: none"></iframe></h:body> </html>
通过导入 iframe 确认 html 文件中的内容可以正常的渲染
最终渲染和现实的结果如下:
WildFly 那边的我就不放了,我已经重新部署了,iframe 的东西是显示不出来了,除非 revert changes
补充代码
这里写了一个小的脚本,因为每次跑完 mvn clean install
都会重新打包 war 和 ear 文件,这也会导致本来的 dorelase
文件被删除,让项目没办法正常部署,还得重新跑一下 docker 的指令,稍微有点麻烦
#!/bin/bashset -eecho "🧨 Step 1: Shutting down any existing containers..."
docker compose downecho "🔧 Step 2: Building jboss-mock module with Maven..."
cd jboss-mock
mvn clean install
cd ..echo "✅ Maven build completed. Artifacts generated in: deployments/"WAR_PATH="./deployments/jboss-mock/webapp-1.0.0.war"
if [ -f "$WAR_PATH" ]; thenecho "📦 Detected .war file. Creating .dodeploy marker to trigger JBoss deployment..."touch "${WAR_PATH}.dodeploy"
elseecho "❌ webapp-1.0.0.war not found! Please check if Maven build succeeded."exit 1
fiecho "🚀 Step 3: Starting container services..."
./start.shecho ""
echo "🎉 Done! You can now visit your app at:"
echo "🔗 http://localhost:8080/webapp-1.0.0/"
echo "🔗 http://localhost:8180/webapp-1.0.0/"
问题定位 & 修复过程
问题定位
其实问题主要出现在这个 <iframe src="angular/angular.html" style="width: 100%; height: 80vh; border: none"></iframe>
这里。前面提到过了,因为 context 没有办法共享的关系,所以在我们实际的生产环境,就需要使用 <iframe src="angular/angular.html?sessionId={someJavaMethod()}" style="width: 100%; height: 80vh; border: none"></iframe>
的方法去调用
明文的 session id 主要有这么几个问题:
- 容易被截获,也会被 bookmark
- 暴露于 iframe,可以被 js 文件读取
- 容易引发 XSS 攻击
- 无法自动过期
总体来说,我是能理解 InfoSec 为什么会 flag 这个实现的,不过实现起来确实有点头疼……
修复过程
目前来说找到的实现方法是使用放在 panelGroup
里,让 AngularJS 在浏览器中继续执行操作,修改的代码如下:
<!DOCTYPE html>
<htmlxmlns="http://www.w3.org/1999/xhtml"xmlns:h="http://xmlns.jcp.org/jsf/html"
><h:head><title>JSF + AngularJS</title><script src="angular/lib/angular/angular.js"></script><script>angular.module("myApp", []).controller("MainCtrl", function ($scope) {$scope.message = "Hello from Angular 1.3!";$scope.items = ["Item A", "Item B", "Item C"];$scope.addItem = function () {$scope.items.push("Item " + ($scope.items.length + 1));};});</script></h:head><h:body><h:panelGroup layout="block"><!-- JSF will ignore {{ }} as long as it's not within EL context --><div ng-app="myApp" ng-controller="MainCtrl"><h2>{{ message }}</h2><input type="text" ng-model="userInput" /><p>You typed: {{ userInput }}</p><ul><li ng-repeat="item in items">{{ item }}</li></ul><button ng-click="addItem()">Add</button></div></h:panelGroup></h:body>
</html>
因为移除了 iframe,所以不会导入 angular.html 了,渲染结果为:
当然,如果要换成动态导入 JS 文件,也是可以实现的:
<!DOCTYPE html>
<htmlxmlns="http://www.w3.org/1999/xhtml"xmlns:h="http://xmlns.jcp.org/jsf/html"
><h:head><title>JSF + AngularJS</title><script src="angular/lib/angular/angular.js"></script></h:head><h:body><h:panelGroup layout="block"><!-- JSF will ignore {{ }} as long as it's not within EL context --><script src="angular/app/main.controller.js"></script><div ng-app="demoApp" ng-controller="MainController"><h2>{{ message }}</h2><input type="text" ng-model="userInput" /><p>You typed: {{ userInput }}</p><ul><li ng-repeat="item in items">{{ item }}</li></ul><button ng-click="addItem()">Add</button></div></h:panelGroup></h:body>
</html>
效果如下:
⚠️:我 debug 的时候眼瘸, ng-app="demoApp" ng-controller="MainController
没有保证一致,所以 debug 的时候搞了好久都失败,然后重新过了一遍 html 才发现是名字的问题
没有继续尝试的方案
如果还失败,我打算试试 <h:outputText escape="false" />
,这个 tag 可以让里面的内容不被转译。目前来说,上面使用 panelGroup
是够了,如果实际运行环境中,用 panelGroup
还不行,那么这个就是我最后的救命稻草了……
失败的尝试方案
这里也简单的说一下一些失败的尝试方案吧……如果有需求也可以试试看,说不定是我漏了什么……
-
没有移除 iframe,但是移除了 session id
渲染的页面直接因为没有 session id 拒绝访问 -
没有用
panelGroup
,直接使用了 HTML tag,但是将 Angular 的所有 attributes 修改为以data-*
开头的格式
这里的想法是 xhtml 的检查比较严格,担心可能没办法认出 customized 的 Angular 属性,所以导致直接跳过,因此用data-*
的方法让 AngularJS 可以识别还是没有办法解决 JSF 的转译问题
小结
目前来说这个方法只是延长一下当前项目的生命周期,作为一个 patch 尚可,作为长期的 solution 就有点力有不怠。真正能够解决方法的还是停用 JSF——WildFly/JBoss 官方其实已经不推荐继续用 JSF 了,尽管因为 legacy code 的问题还是继续提供支持,不过也停止了对 JSF3.0 的原生支持
有条件的话还是得往现在主流的 SPA 迁徙,前端御三家选哪个都行……