返回

Vaadin 24+ 样式表和 JavaScript 外置化:安全性与最佳实践

java

Vaadin 应用程序样式表和 JavaScript 代码外置化

在使用 Spring Boot 和 Vaadin (版本 24.6.0) 开发应用程序时,我遇到了一个问题:如何将样式表外置,以提高在受限生产环境中的安全性。虽然样式能正常工作,但即使在外置样式表后,仍能通过浏览器的开发者工具在 HTML 代码中看到所有 CSS 类的内容。目标是通过使用 Vaadin 的外部样式表支持,消除内部样式表的使用,从而增强安全性。

当前,我已经按照以下步骤操作:

  • custom-dashboard-view.css 中添加了自定义样式。
  • 通过 @import 将其映射到 style.css
  • Application.java 中指定了自定义主题 "dashboard-app"。
  • CustomDashboardView.java 中应用了自定义类。
  • 通过 Maven 的 vaadin:build-frontend goal 构建了应用程序。

样式可以正常使用,但在外置样式表之后,仍然可以通过浏览器的开发者工具在 HTML 代码中查看 CSS 类的全部内容。

怎么彻底把 Vaadin 生成的样式表和 JavaScript 代码外置?

问题原因分析

问题在于对 Vaadin 样式外置化的理解,以及对 Web 应用程序工作方式的理解存在偏差。要搞清楚几点:

  1. “外置”的含义: Vaadin 的样式外置化,指的是将 CSS 样式从 Java 代码(通过 @StyleSheet 注解或内联样式)移动到单独的 CSS 文件中。这有助于代码组织和可维护性,但并不意味着这些样式对浏览器隐藏。
  2. 浏览器的开发者工具: 任何前端框架(包括 Vaadin)生成的 HTML、CSS 和 JavaScript,最终都会在浏览器中呈现。浏览器的开发者工具是用来检查和调试这些前端代码的。因此,只要样式应用于页面元素,就能通过开发者工具看到。
  3. Vaadin 的工作方式: Vaadin 是一个服务器端框架。这意味着 UI 逻辑主要在服务器上运行,客户端(浏览器)接收的是最终的 HTML、CSS 和 JavaScript。即使使用了外部样式表,这些样式表仍然需要被浏览器加载和解析,才能应用到页面元素上。
  4. 安全性: 单纯将样式表外置,并不能直接提高安全性。因为样式表本身并不包含敏感信息,通常暴露的是组件结构和样式类名。真正的安全问题,来自于后端逻辑的暴露和数据泄露。

所以,想要通过“外置”样式表来隐藏 CSS 类内容是不现实的,因为浏览器渲染页面必须依赖这些样式。但如果关注点是防止对CSS文件的直接、未授权的访问, 可以做一些安全措施.

解决方案

下面分几点给出建议的解决思路,及代码例子:

1. 正确理解样式表外置

首先确保已经正确完成了样式表的外置:

  • 创建样式表文件: 通常在 frontend/styles 目录下创建 CSS 文件(例如 shared-styles.cssviews/my-view.css)。

  • 使用 @Import 导入 (如果需要): 你可以在一个主要的 CSS 文件 (通常是 styles.css) 中使用 @import 导入其他的 CSS 文件.

    /* styles.css */
    @import './shared-styles.css';
    @import './views/my-view.css';
    
  • 使用@CssImport关联样式到组件: 在你的 Vaadin 视图或者组件的 Java 代码中, 用@CssImport导入对应的样式文件。

```java
// MyView.java
@Route("my-view")
@CssImport("./styles/views/my-view.css")
public class MyView extends VerticalLayout {
    // ...
}
```
  • vaadin:build-frontend构建: 在打包构建的时候确保执行vaadin:build-frontend.

这样操作之后,所有的样式信息就被放到了CSS文件里。

2. JavaScript 代码外置

和样式表外置类似,可以把自定义的JavaScript代码外置到 .js文件,通过@JsModule或者@JavaScript导入。

  • 创建.js文件: 比如frontend/js/my-script.js.

  • 编写JavaScript代码:

     // frontend/js/my-script.js
     window.MyNamespace = {
         myFunction: function() {
             console.log("Hello from external JS!");
         }
     };
    
  • 在Vaadin视图中引入:

    //MyView.java
     @JsModule("./js/my-script.js")
     public class MyView extends VerticalLayout{
       //...
       public void someMethod(){
        getElement().executeJs("window.MyNamespace.myFunction()");
       }
     }
    

3. 加强 Web 应用程序安全性(重点)

更重要的, 是做好应用层面的安全, 而不是执着于隐藏 CSS. 以下几点是提高 Web 应用程序安全性的关键措施,这些才是“正道”:

  • 保护敏感端点: 确保 Spring Security 正确配置,对需要授权的 API 端点和 Vaadin 视图进行保护。只有经过身份验证和授权的用户才能访问这些资源。

    // SecurityConfiguration.java (示例)
    @EnableWebSecurity
    public class SecurityConfiguration extends VaadinWebSecurity {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            super.configure(http);
            setLoginView(http, LoginView.class); // 设置登录视图
    
            // 对需要保护的路由进行配置
             http.authorizeRequests().requestMatchers(new AntPathRequestMatcher("/admin/**")).hasRole("ADMIN");
        }
       // ...其他安全配置
    }
    
  • 不要在客户端存储敏感信息 。 敏感信息例如密码,密钥等绝对不应该存储在本地存储(localStorage, sessionStorage)或Cookie中。

  • 数据验证: 在服务器端对所有用户输入进行严格的验证和清理。防止 XSS(跨站脚本攻击)和 SQL 注入等常见 Web 攻击。

  • 使用 HTTPS: 使用 HTTPS 加密客户端和服务器之间的通信。防止中间人攻击。获得 SSL/TLS 证书,并在服务器上配置 HTTPS。

  • 设置 HTTP 安全标头:

    • Content Security Policy (CSP): 控制浏览器可以加载哪些资源。减少 XSS 攻击的风险。
    • X-Frame-Options: 防止点击劫持攻击。
    • X-XSS-Protection: 启用浏览器的 XSS 过滤器。
    • X-Content-Type-Options: 防止 MIME 类型嗅探攻击。
    • Strict-Transport-Security (HSTS): 强制浏览器使用 HTTPS 连接。

    在 Spring Boot 中,可以通过配置 WebSecurityConfigurerAdapter 来设置这些标头:

      @Override
      protected void configure(HttpSecurity http) throws Exception {
          // ... 其他配置
           http.headers()
      	.contentSecurityPolicy("default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:")
         	 .and().frameOptions().deny()
         	 .and().xssProtection().block(true)
         	 .and().contentTypeOptions()
          .and().httpStrictTransportSecurity().includeSubDomains(true).maxAgeInSeconds(31536000);
    
        //...其他配置
    }
    
  • CSRF 防护: Vaadin 已经内置了对 CSRF(跨站请求伪造)的保护. 但是你依然要保证业务逻辑上没有CSRF的漏洞。

  • 定期更新 : 更新 Vaadin, Spring Boot 以及所有依赖的库到最新版本,修复已知的安全漏洞。

4. 进阶使用技巧: CSS 混淆(Obfuscation)和 压缩

虽然无法完全阻止通过开发者工具查看CSS, 但是你可以使用CSS混淆工具, 增大反向工程的难度.

  • CSS 混淆: 使用工具(例如 CSSO, UglifyCSS, cssnano)对 CSS 代码进行混淆,将类名和 ID 替换为难以理解的短名称。这并不能完全阻止他人理解你的样式,但会增加阅读和修改的难度.
  • CSS压缩: 同样是使用这些工具, 可以把CSS文件里不必要的空格, 注释全部删除, 减少文件体积.

可以在Maven的构建流程中集成CSS混淆和压缩. 例如, 使用 yuicompressor-maven-plugin:

<plugin>
    <groupId>net.alchim31.maven</groupId>
    <artifactId>yuicompressor-maven-plugin</artifactId>
    <version>1.5.1</version>
    <executions>
        <execution>
            <goals>
                <goal>compress</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <webappDirectory>${project.build.directory}/${project.build.finalName}/VAADIN/static/VAADIN/build</webappDirectory>

       <excludes>
            <exclude>**/*.js</exclude>
        </excludes>
        <suffix>.gz</suffix>
    </configuration>
</plugin>

这段配置将会在vaadin:build-frontend执行后, 进一步处理生成的CSS文件. 将其压缩并重命名, 例如styles-1234ABCD.css变成styles-1234ABCD.gz.css, 把文件名加上.gz后缀, 文件内容混淆+压缩.

(请注意, 这样的配置会使得最终的CSS文件名改变, 如果其他地方有硬编码依赖于这个文件名, 需要对应修改).

总而言之,真正的安全应该着眼于服务器端逻辑、数据验证、HTTPS 和 HTTP 安全标头等方面。外置样式表主要是为了代码组织和可维护性,并不能阻止他人通过浏览器的开发者工具查看CSS. 对于想要提高反向工程CSS的难度的情况, 可以考虑混淆手段.