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 遷徙,前端御三家選哪個都行……