【问题标题】:How to package React front end with Spring Boot and run the executable jar?如何使用 Spring Boot 打包 React 前端并运行可执行 jar?
【发布时间】:2020-05-18 19:01:47
【问题描述】:

我有一个包含 REST 服务的 Java Spring Boot 后端应用程序。在主 Spring Boot 项目文件夹中,我有一个用于前端的 React 应用程序。我可以运行 Spring Boot 应用程序并成功访问所有端点。我可以运行 React 应用程序,它也可以工作。但现在我想创建一个可执行的 jar 文件并将其作为单个应用程序运行,而不是两个。

我像这样创建一个可执行的 fat jar 文件:

mvn clean install

它会创建一个 jar 文件。当我运行它时

java -jar target/medaverter-0.0.1-SNAPSHOT.jar

后端启动正常,但前端不会像我单独运行时那样在浏览器窗口中弹出

npm start

我已经按照这些教程来了解我现在的位置。显然我错过了一些东西。 https://medium.com/@mukundmadhav/build-and-deploy-react-app-with-spring-boot-and-mysql-6f888eb0c600#37fa https://blogg.kantega.no/webapp-with-create-react-app-and-spring-boot/

这里是 pom.xml 文件。插件部分在 React 应用程序中实现了合并的魔力:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.7.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>net.tekknow</groupId>
    <artifactId>medaverter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>medaverter</name>
    <description>Demo project for Spring Security</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20190722</version>
        </dependency>   
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.6</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
               <groupId>com.github.eirslett</groupId>
               <artifactId>frontend-maven-plugin</artifactId>
               <version>1.6</version>
               <configuration>
                   <workingDirectory>medaverter-front</workingDirectory>
                   <installDirectory>target</installDirectory>
               </configuration>
               <executions>
                   <execution>
                       <id>install node and npm</id>
                       <goals>
                           <goal>install-node-and-npm</goal>
                       </goals>
                       <configuration>
                           <nodeVersion>v8.9.4</nodeVersion>
                           <npmVersion>5.6.0</npmVersion>
                       </configuration>
                   </execution>
                   <execution>
                       <id>npm install</id>
                       <goals>
                           <goal>npm</goal>
                       </goals>
                       <configuration>
                           <arguments>install</arguments>
                       </configuration>
                   </execution>
                   <execution>
                       <id>npm run build</id>
                       <goals>
                           <goal>npm</goal>
                       </goals>
                       <configuration>
                           <arguments>run build</arguments>
                       </configuration>
                   </execution>
               </executions>
            </plugin>
            <plugin>
                <artifactId>maven-antrun-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>generate-resources</phase>
                        <configuration>
                            <target>
                                <copy todir="${project.build.directory}/classes/public">
                                    <fileset dir="${project.basedir}/medaverter-front/build"/>
                                </copy>
                            </target>
                        </configuration>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
        <pluginManagement>
            <plugins>
                <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
                <plugin>
                    <groupId>org.eclipse.m2e</groupId>
                    <artifactId>lifecycle-mapping</artifactId>
                    <version>1.0.0</version>
                    <configuration>
                        <lifecycleMappingMetadata>
                            <pluginExecutions>
                                <pluginExecution>
                                    <pluginExecutionFilter>
                                        <groupId>
                                            org.apache.maven.plugins
                                        </groupId>
                                        <artifactId>
                                            maven-antrun-plugin
                                        </artifactId>
                                        <versionRange>
                                            [1.8,)
                                        </versionRange>
                                        <goals>
                                            <goal>run</goal>
                                        </goals>
                                    </pluginExecutionFilter>
                                    <action>
                                        <ignore></ignore>
                                    </action>
                                </pluginExecution>
                            </pluginExecutions>
                        </lifecycleMappingMetadata>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

我已验证 react 文件夹中名为 medaverter-front 的相同文件已复制到 target/classes/public 文件夹。

我在 package.json 文件中设置了以下代理:

  "proxy": {
    "/api": {
      "target": "http://localhost:8080",
      "ws": true
    }
  },

如果我进入浏览器: http://localhost:8080/api/test/all

我在浏览器中看到: “公共内容”

如果我直接访问后端,我应该这样做,但终端显示:

n.t.m.security.jwt.AuthEntryPointJwt:未经授权的错误:已满 访问此资源需要身份验证

如果我进入浏览器: http://localhost:8080/home

它返回“出现意外错误(类型=未授权,状态=401)。”

显然 Spring Security JWT 以某种方式干扰,但我就是想不通。主页不需要任何身份验证,但有些东西就像它一样。这是后端树:

我还应该提到,在应用启动期间,我看到了以下几个异常:

java.sql.SQLNonTransientConnectionException:公钥检索是 不允许

但这并不能阻止它运行。我通过将“&allowPublicKeyRetrieval=true”添加到 application.properties 文件中的 spring.datasource.url 解决了这个问题。

【问题讨论】:

  • 尝试删除依赖 spring-boot-starter-security 进行确认,如果没有正确配置 spring security 可能会尝试保护所有 url。
  • 好主意。我试过了,但构建失败,出现 81 个错误。
  • 试试这个github.com/sachin073/spring-security,这也是一个带有嵌入式JSP作为jar本身前端的spring安全演示应用程序。您的问题与安全相关。尝试参考安全配置。
  • 感谢您的链接。我克隆了这个项目,发现它是一个 gradle 项目。我从来没有使用过gradle。我安装了 gradle 并尝试使用“gradlew build”构建您的项目,但失败并显示“找不到 org.springframework.boot:spring-security-config”

标签: java reactjs spring-boot spring-security


【解决方案1】:

我终于找到了问题所在。我加了

    .antMatchers("/home/**").permitAll()
    .antMatchers("/**").permitAll()

到 WebSecurityConfig.java 文件中的配置方法。现在看起来像这样:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().and().csrf().disable()
        .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
        .authorizeRequests().antMatchers("/api/auth/**").permitAll()
        .antMatchers("/api/test/**").permitAll()
        .antMatchers("/home/**").permitAll()
        .antMatchers("/**").permitAll()
        .anyRequest().authenticated();

    http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}

我对这种方法感到困惑(现在仍然如此)。我曾认为如果 /api/test/all 被处理,这就是我所需要的。显然不是。为什么我会这么想? React 流程从 App.js 开始,其中包含

      <Route exact path={["/", "/home"]} component={Home} />

home.component.js 包含:

  componentDidMount() {
    UserService.getPublicContent().then(
      response => {
        this.setState({
          content: response.data
        });
      },

user.service.js 包含:

const API_URL = 'http://localhost:8080/api/test/';

class UserService {
  getPublicContent() {
    return axios.get(API_URL + 'all');
  }

SpringBoot TestController.java 处理后端调用:

@RequestMapping("/api/test")
public class TestController {
    @GetMapping("/all")
    public String allAccess() {
        return "Public Content.";
    }

因此,/ 或 /home 最终会调用 http://localhost:8080/api/test/all 返回“公共内容”。

【讨论】:

    猜你喜欢
    • 2017-05-12
    • 2018-05-26
    • 1970-01-01
    • 1970-01-01
    • 2018-10-17
    • 1970-01-01
    • 2018-04-06
    • 2014-12-18
    • 2016-02-24
    相关资源
    最近更新 更多