【官方教程翻译】SpringBoot 允许跨域请求

原文:spring.io/guides/gs/r…

在项目初始化的部分有删减

本文将带你创建允许跨域资源共享( CORS,Corss-Origin Resource Sharing )的 web 服务。更多关于 Spring CORS 支持的信息可阅读 该博客(译注:已在翻译计划中)。

最终目标

本文最终的目标,是带你开发一个能够在 http://localhost:8080/greeting 接收 HTTP GET 请求的服务,该接口将以 JSON 格式返回一个问候信息,如:

{"id":1,"content":"Hello, World!"}
复制代码

该接口还能够通过 url 中的查询字符串( query string )接收一个可选的 name 参数,请求如:

http://localhost:8080/greeting?name=User
复制代码

这里的 name 参数值将覆盖默认的 World ,此时的接口响应如下:

{"id":1,"content":"Hello, User!"}
复制代码

该服务和另一篇文章 SpringBoot 开发 RESTful Web 服务 稍有不同,主要区别在于本文所述的服务使用了 Spring 框架的 CORS 支持,在 HTTP 响应头中添加了 CORS 。

如何完成该教程

和其他的 Spring 教程一样,你可以从零开始建立项目,也可以直接使用已有的代码模板。

  • 若要从零开始,请阅读从零开始建立项目

  • 若要跳过创建项目的部分,请进行如下步骤:

    • 下载本教程的源代码:github.com/spring-guid…
    • 进入 gs-rest-service-cors/initial 目录
    • 直接阅读本文下面的【创建资源表示类】部分
  • 当完成代码后,可以将自己的代码和 gs-rest-service-cors/complete 中的代码进行比较,检查自己的成果。

添加 httpclient 依赖

本代码对应的测试代码(complete/src/test/java/com/example/restservicecors/GreetingIntegrationTests.java)需要用到 Apache 的 httpclient 库。

要在 Maven 中添加 Apache httpclient ,需要在 pom 中添加下面的依赖:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <scope>test</scope>
</dependency>
复制代码

最终的 pom.xml 如下:

<?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.6.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>rest-service-cors-complete</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rest-service-cors-complete</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies><build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build></project>
复制代码

Gradle 添加如下:

testImplementation 'org.apache.httpcomponents:httpclient'
复制代码

完成的 Gradle 如下:

plugins {
    id 'org.springframework.boot' version '2.6.3'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}
​
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'repositories {
    mavenCentral()
}
​
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.apache.httpcomponents:httpclient'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
​
test {
    useJUnitPlatform()
}
复制代码

创建资源表示类(Resource Representation Class)

首先,我们来思考一下服务交互的过程:

服务首先应该接收 /greeting url 上面的 GET 请求,并处理可选的 name 参数。GET 请求应该返回一个 200 OK 的响应并在响应体( body )部分加上最终的 JSON ,样子如下:

{
    "id":1,
    "content":"Hello, World!"
}
复制代码

id 字段是每个问候响应的唯一标识符,content 是问候文本。

我们需要建立一个资源表示类( resource representation class )作为问候响应的数据模型。这个类是一个包含数据(表示 idcontent 的变量其访问他们的方法)、构造器的普通 Java 类,代码如下:

package com.example.restservice;
​
public class Greeting {
    
    private final long id;
    private final String content;
    
    public Greeting(long id, String content) {
        this.id = id;
        this.content = content;
    }
    
    public long getId() {
        return id;
    }
    
    public String getContent() {
        return content;
    }
}
复制代码

本应用程序使用 Jackson JSON 库来自动将 Greeting 对象转为 JSON。Jackson 默认包含在 Web 初始模板中

创建资源控制器(Resource Controller)

在 Spring 的 RESTful Web 服务中,HTTP 请求都是由控制器来管理的。这些控制器部件都被 @RestController 注解修饰,下面代码中的 GreetingController 会在 /greeting 接收 GET 请求,并返回一个新创建的 Greeting 类实例:

package com.example.restservice;
​
import java.util.concurrent.atomic.AtomicLong;
​
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
​
@RestController
public class GreetingController {
    
    private static final String template = "Hello,%s!";
    private final AtomicLong counter = new AtomicLong();
    
    @CrossOrigin(origins="http://localhost:8080")
    @GetMapping("/greeting")
    public Greeting greeting(@RequestParam(value="name", defaultValue="World") String name) {
        System.out.printLn("====get greeting====");
        return new Greeting(counter.incrementAndGet(), String.format(template, name));
    }
}
复制代码

下面我们来一步步解读上面的代码:

@GetMapping 注解让到 /greeting 的请求映射到 greeting() 方法。

此外还有几个处理其他 HTTP 方法的注解(例如给 POST 用的 @PostMapping ),它们都派生自 @RequestMapping 注解。也可以通过 @RequestMapping(method=GET) 的方法来直接使用 RequestMapping。

Spring 仍然会拒绝 origin 与 CORS 配置不相符的 GET 请求。浏览器不需要发送 CORS 预检请求(Preflight request,浏览器用该请求来检查服务器是否支持 CORS)。但我们我们想要触发预检,可以通过 @PostMapping 来接收请求 body 中的 JSON

@RequestParam 能够将请求中的查询字符串参数 name 转为 greeting() 方法的 name 参数。如果请求中不含 name 参数,则将使用defaultValue 的值 World 作为默认参数。

方法体中创建并返回一个 Greeting 实例,并设置其成员变量 counter(通过 incrementAndGet() 方法) 和 content (通过对 templatename 参数来格式化)的值。

传统 MVC 控制器和 RESTful web 服务控制器的关键差异在于他们创建 HTTP 响应体的方式:传统 MVC 会使用服务端渲染(SSR)的方式,将问候数据直接转为 HTML 。而这里的 RESTful web 服务控制器会生成并返回一个 Greeting 对象。该对象的数据会被直接以 JSON 的方式返回给请求者。

为了实现该功能,@RestController 注解假设每个方法都默认继承了 @ResponseBody 语义。因此,被返回的对象数据会被直接插入到响应体中。

Greeting 对象需要被转为 JSON 才能返回给请求者,该工作由 Spring HTTP 消息转换支持来实现,因此不需要我们对其进行手动转换。因为 classpath 中包含 Jackson 2 ,因此 Spring 会自动选择 MappingJackson2HttpMessageConverter 来将 Greeting 实例转为 JSON 文本。

启用 CORS

用户可以在每个 controller 中分别启用 CORS ,也可以全局启用。下面分别讲述这两种用法:

为控制器方法配置 CORS

因为 RESTful web 服务本身就会在响应中包含 CORS 访问控制头,因此可以直接在需要提供 CORS 支持的方法前添加 @CrossOrigin 注解。例子如下:

@CrossOrigin(origins="http://localhost:8080")
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(required=false, defaultValue="World") String name) {
    //...
}
复制代码

这里的 @CrossOrigin 注解只会为该方法启用跨域资源共享。默认情况下,其会允许所有的 origin,所有的 header 和由 @RequestMapping 注解指定的 HTTP 方法。你还可以通过在注解中添加下面的注解属性值来影响跨域的行为:

  • origins
  • methods
  • allowedHeaders
  • exposedHeaders
  • allowCredentials
  • maxAge

在上面的例子中,我们只允许 http://localhost:8080 发送跨域请求。

你还可以在控制器类上面添加 @CrossOrigin 注解,这会为该类中的所有方法启用 CORS

配置全局 CORS

除了像上文中那样仔细控制跨域之外,我们还可以定义一些全局的 CORS 配置。该方法与使用 Filter 类似,但可以在 Spring MVC 中声明,并可以和单独配置的 @CrossOrigin 配置混合使用。默认情况下会允许所有的 origin 、GETHEAD,以及POST方法。

下面的代码展示了 GreetingController 类中的 greetingWithJavaconfig 方法:

@GetMapping("/greeting-javaconfig")
public Greeting greetingWithJavaConfig(@RequestParam(required=false, defaultValue="World") String name) {
    System.out.println("====in greeting====");
    return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
复制代码

greetingWithJavaconfig 方法和前文中用的 greeting 方法的不同之处在于路由地址和是否写了 @CrossOrigin

下面的代码展示如何在应用类中添加 CORS (代码位于 /src/main/java/com/example/restservicecors/RestServiceCorsApplication.java):

public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/greeting-javaconfig").allowOrigins("http://localhost:8080");
        }
    }
}
复制代码

我们可以在这里修改任何配置(比如上面例子中的 allowedOrigins ),也可以将 CORS 配置应用到某个具体的请求路径上。

全局配置可以和单独配置的 @CrossOrigin 配置混合使用

创建应用类

Spring Initializr 为我们创建了一个最基础的应用类,如下:

package com.example.restservicecors;
​
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
​
@SpringBootApplication
public class RestServiceCorsApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(RestServiceCorsApplication.class, args);
    }
​
}
复制代码

我们需要在这个模板的基础上,添加前文所述的处理跨域资源共享的配置方法,如下:

@bean
public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:8080");
        }
    }
}
复制代码

完整的应用类代码如下:

package com.example.restservicecors;
​
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
​
@SpringBootApplication
public class RestServiceCorsApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(RestServiceCorsApplication.class, args);
    }
​
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:8080");
            }
        };
    }
​
}
复制代码

@SpringBootApplication 是下面三个注解的简写:

  • @Configuration : 将类标记为应用上下文( application context )bean 的定义;
  • @EnableAutoConfiguration :让 Spring Boot 根据 classpath 设置、其他的属性设置( various property settings )等来添加 bean 。例如:如果 classpath 中有一个 spring-webmvc ,那么该标记就会将应用程序标记为一个 web 应用,并启用相关的行为——比如启动一个 DispatcherServlet
  • @ComponentScan :让 Spring 寻找在 com/example 包下面的其他组件、配置、服务等,并找到所有的控制器。

创建可执行 JAR

写好的程序可以通过命令行或使用 Gradle / Maven 来运行。此外我们也可以将应用构建为一个可执行的 JAR 文件,该文件会包含所有必须的依赖、类和运行所需的资源。构建可执行 JAR 能够方便发布、管理版本以及在不同的环境中部署等。

如果你用 Gradle 管理项目,那么可以使用 ./gradlew bootRun 来运行应用程序。如果想将程序构建为 JAR ,则可以使用 ./gradlew build 构建然后运行,运行方法如下:

java -jar build/libs/gs-rest-service-cors-0.1.0.jar
复制代码

如果你用 Maven 管理项目,可以通过 ./mvnw spring-boot:run 运行程序。如果要构建为 JAR ,使用命令 ./mvnw clean package 并通过和上面一样的方式运行 jar 文件。

测试服务

测试 API 基本功能可用

程序运行起来之后,使用浏览器访问 http://localhost:8080/greeting ,可以看到

{"id":1,"content":"Hello, World!"}
复制代码

也可以在 url 中加上查询参数:http://localhost:8080/greeting?name=User ,显示如下:

{"id":2,"content":"Hello, User!"}
复制代码

测试 CORS:

要测试 CORS ,需要一个客户端(前端)。该客户端在 通过 jQuery 使用 RESTful Web 服务 一文中有具体的讲解。

创建两个文件,首先是进行网络请求的 js 文件,我们将其命名为 hello.js

$(document).ready(function() {
    $.ajax({
        url: "http://localhost:8080/greeting"
    }).then(function(data, status, jqxhr) {
       $('.greeting-id').append(data.id);
       $('.greeting-content').append(data.content);
       console.log(jqxhr);
    });
});
复制代码

该脚本使用 jQuery 请求 localhost:8080/greeting ,其将被下面的 index.html 引入并调用:

<!DOCTYPE html>
<html>
    <head>
        <title>Hello CORS</title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
        <script src="hello.js"></script>
    </head>
​
    <body>
        <div>
            <p class="greeting-id">The ID is </p>
            <p class="greeting-content">The content is </p>
        </div>
    </body>
</html>
复制代码

使用启动该站点后(译注:直接使用浏览器打开 index.html 文件是不行的,需要为该文件起一个 Web 服务器。详见 通过 jQuery 使用 RESTful Web 服务 一文),访问前端,即可看到网页能够成功取回数据,这说明服务器接受了来自 localhost:8080 的请求

image.png

为了测试 CORS,我们可以将该前端的运行端口改为 9000 ,此时便无法取回数据了。

image.png

猜你喜欢

转载自juejin.im/post/7076447956399816711