JBoss 项目修复笔记:绕开 iframe 安全问题,JSF 与 Angular 最小代价共存方案

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>
          <input
            type="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>
    <html
      xmlns="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>
    
        <iframe
          src="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/bash

set -e

echo "🧨 Step 1: Shutting down any existing containers..."
docker compose down

echo "🔧 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" ]; then
  echo "📦 Detected .war file. Creating .dodeploy marker to trigger JBoss deployment..."
  touch "${WAR_PATH}.dodeploy"
else
  echo "❌ webapp-1.0.0.war not found! Please check if Maven build succeeded."
  exit 1
fi

echo "🚀 Step 3: Starting container services..."
./start.sh

echo ""
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>
<html
  xmlns="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>
<html
  xmlns="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 迁徙,前端御三家选哪个都行……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值