{% raw %}

Spring Boot CORS 教程

原文: http://zetcode.com/springboot/cors/

Spring Boot CORS 教程显示了如何在 Spring Boot 应用中设置跨域资源共享。

CORS

跨域资源共享(CORS)是一种安全策略,它使用 HTTP 标头来告诉浏览器,让运行在一个来源(域)上的 Web 应用有权访问来自另一个来源的服务器中的选定资源。

网页可以嵌入跨域图像,样式表,脚本,iframe和视频。 默认情况下,同源安全策略禁止某些跨域请求,尤其是 Ajax 请求。

XMLHttpRequest和 Fetch API 遵循同源策略。 因此; 使用这些 API 的 Web 应用只能从加载应用的相同来源请求 HTTP 资源,除非来自其他来源的响应包括正确的 CORS 标头。

Spring Boot CORS 示例

以下 Spring Boot 应用将 Angular 用作前端。 Angular SPA 在localhost:4200上运行,并向在localhost:8080上运行的 Spring Boot 后端发出请求。 为此,我们需要在 Spring Boot 应用中启用 CORS。

Spring Boot 后端

后端将在 Spring Boot 中创建。

  1. pom.xml
  2. src
  3. ├───main
  4. ├───java
  5. └───com
  6. └───zetcode
  7. Application.java
  8. MyRunner.java
  9. ├───config
  10. AppConf.java
  11. ├───controller
  12. MyController.java
  13. ├───model
  14. City.java
  15. └───repository
  16. CityRepository.java
  17. └───resources
  18. application.properties
  19. └───static
  20. index.html
  21. └───test
  22. └───java

这是项目结构。

pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
  5. http://maven.apache.org/xsd/maven-4.0.0.xsd">
  6. <modelVersion>4.0.0</modelVersion>
  7. <groupId>com.zetcode</groupId>
  8. <artifactId>corsex</artifactId>
  9. <version>1.0-SNAPSHOT</version>
  10. <packaging>jar</packaging>
  11. <properties>
  12. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  13. <maven.compiler.source>11</maven.compiler.source>
  14. <maven.compiler.target>11</maven.compiler.target>
  15. </properties>
  16. <parent>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-parent</artifactId>
  19. <version>2.1.5.RELEASE</version>
  20. </parent>
  21. <dependencies>
  22. <dependency>
  23. <groupId>org.springframework.boot</groupId>
  24. <artifactId>spring-boot-starter-web</artifactId>
  25. </dependency>
  26. <dependency>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-starter-data-jpa</artifactId>
  29. </dependency>
  30. <dependency>
  31. <groupId>com.h2database</groupId>
  32. <artifactId>h2</artifactId>
  33. <scope>runtime</scope>
  34. </dependency>
  35. </dependencies>
  36. <build>
  37. <plugins>
  38. <plugin>
  39. <groupId>org.springframework.boot</groupId>
  40. <artifactId>spring-boot-maven-plugin</artifactId>
  41. </plugin>
  42. </plugins>
  43. </build>
  44. </project>

这是 Maven 构建文件。

resources/application.properties

  1. spring.main.banner-mode=off

application.properties是主要的 Spring Boot 配置文件。 使用spring.main.banner-mode属性,我们可以关闭 Spring 横幅。

com/zetcode/model/City.java

  1. package com.zetcode.model;
  2. import java.util.Objects;
  3. import javax.persistence.Entity;
  4. import javax.persistence.GeneratedValue;
  5. import javax.persistence.GenerationType;
  6. import javax.persistence.Id;
  7. import javax.persistence.Table;
  8. @Entity
  9. @Table(name = "cities")
  10. public class City {
  11. @Id
  12. @GeneratedValue(strategy = GenerationType.AUTO)
  13. private Long id;
  14. private String name;
  15. private int population;
  16. public City() {
  17. }
  18. public City(String name, int population) {
  19. this.name = name;
  20. this.population = population;
  21. }
  22. public Long getId() {
  23. return id;
  24. }
  25. public void setId(Long id) {
  26. this.id = id;
  27. }
  28. public String getName() {
  29. return name;
  30. }
  31. public void setName(String name) {
  32. this.name = name;
  33. }
  34. public int getPopulation() {
  35. return population;
  36. }
  37. public void setPopulation(int population) {
  38. this.population = population;
  39. }
  40. @Override
  41. public int hashCode() {
  42. int hash = 7;
  43. hash = 79 * hash + Objects.hashCode(this.id);
  44. hash = 79 * hash + Objects.hashCode(this.name);
  45. hash = 79 * hash + this.population;
  46. return hash;
  47. }
  48. @Override
  49. public boolean equals(Object obj) {
  50. if (this == obj) {
  51. return true;
  52. }
  53. if (obj == null) {
  54. return false;
  55. }
  56. if (getClass() != obj.getClass()) {
  57. return false;
  58. }
  59. final City other = (City) obj;
  60. if (this.population != other.population) {
  61. return false;
  62. }
  63. if (!Objects.equals(this.name, other.name)) {
  64. return false;
  65. }
  66. return Objects.equals(this.id, other.id);
  67. }
  68. @Override
  69. public String toString() {
  70. var builder = new StringBuilder();
  71. builder.append("City{id=").append(id).append(", name=")
  72. .append(name).append(", population=")
  73. .append(population).append("}");
  74. return builder.toString();
  75. }
  76. }

这是City实体。 它包含以下属性:idnamepopulation

com/zetcode/repository/CityRepository.java

  1. package com.zetcode.repository;
  2. import com.zetcode.model.City;
  3. import org.springframework.data.jpa.repository.JpaRepository;
  4. import org.springframework.stereotype.Repository;
  5. @Repository
  6. public interface CityRepository extends JpaRepository<City, Long> {
  7. }

CityRepositoryJpaRepository延伸。 它提供了实体的类型及其主键。

com/zetcode/controller/MyController.java

  1. package com.zetcode.controller;
  2. import com.zetcode.model.City;
  3. import com.zetcode.repository.CityRepository;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.RestController;
  7. import java.util.List;
  8. @RestController
  9. public class MyController {
  10. @Autowired
  11. private CityRepository cityRepository;
  12. @GetMapping(value = "/cities")
  13. public List<City> cities() {
  14. return cityRepository.findAll();
  15. }
  16. }

MyController中,我们有一个返回所有城市的端点。

注意:在Java企业应用中,定义与存储库一起使用的服务层是一个好习惯。 为简单起见,我们跳过服务层。

com/zetcode/conf/AppConf.java

  1. package com.zetcode.config;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.web.servlet.config.annotation.CorsRegistry;
  4. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  5. @Configuration
  6. public class AppConf implements WebMvcConfigurer {
  7. @Override
  8. public void addCorsMappings(CorsRegistry registry) {
  9. registry.addMapping("/**")
  10. .allowedOrigins("http://localhost:4200")
  11. .allowedMethods("GET");
  12. }
  13. }

使用CorsRegistry,我们启用 CORS。 我们设置允许的来源和请求方法。

com/zetcode/MyRunner.java

  1. package com.zetcode;
  2. import com.zetcode.model.City;
  3. import com.zetcode.repository.CityRepository;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.boot.CommandLineRunner;
  8. import org.springframework.stereotype.Component;
  9. @Component
  10. public class MyRunner implements CommandLineRunner {
  11. private static final Logger logger = LoggerFactory.getLogger(MyRunner.class);
  12. @Autowired
  13. private CityRepository cityRepository;
  14. @Override
  15. public void run(String... args) throws Exception {
  16. logger.info("Saving cities");
  17. cityRepository.save(new City("Bratislava", 432000));
  18. cityRepository.save(new City("Budapest", 1759000));
  19. cityRepository.save(new City("Prague", 1280000));
  20. cityRepository.save(new City("Warsaw", 1748000));
  21. cityRepository.save(new City("Los Angeles", 3971000));
  22. cityRepository.save(new City("New York", 8550000));
  23. cityRepository.save(new City("Edinburgh", 464000));
  24. }
  25. }

MyRunner中,我们将数据添加到内存 H2 数据库中。

resources/static/index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Home page</title>
  6. </head>
  7. <body>
  8. <p>
  9. This is home page
  10. </p>
  11. <script>
  12. fetch('http://localhost:8080/cities')
  13. .then(res => res.json())
  14. .then(data => console.log('Output: ', data))
  15. .catch(err => console.error(err));
  16. </script>
  17. </body>
  18. </html>

在主页中,我们使用 Fetch API 创建一个获取所有城市的请求。 该请求来自同一来源,因此此处不需要 CORS。

com/zetcode/Application.java

  1. package com.zetcode;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class Application {
  6. public static void main(String[] args) {
  7. SpringApplication.run(Application.class, args);
  8. }
  9. }

Application设置 Spring Boot 应用。

Angular 前端

应用的前端是使用 Angular 创建的。

  1. $ npm i -g @angular/cli
  2. $ ng new frontend
  3. $ cd frontend

我们创建一个新的 Angular 应用。

src/app/app.module.ts

  1. import { BrowserModule } from '@angular/platform-browser';
  2. import { NgModule } from '@angular/core';
  3. import { HttpClientModule } from '@angular/common/http';
  4. import { AppComponent } from './app.component';
  5. @NgModule({
  6. declarations: [
  7. AppComponent
  8. ],
  9. imports: [
  10. BrowserModule,
  11. HttpClientModule
  12. ],
  13. providers: [],
  14. bootstrap: [AppComponent]
  15. })
  16. export class AppModule { }

app.module.ts中,我们启用了 http 模块,该模块用于发出请求。

src/app/app.component.ts

  1. import { Component, OnInit } from '@angular/core';
  2. import { HttpClient } from '@angular/common/http';
  3. @Component({
  4. selector: 'app-root',
  5. templateUrl: './app.component.html',
  6. styleUrls: ['./app.component.css']
  7. })
  8. export class AppComponent implements OnInit {
  9. constructor(private http: HttpClient) { }
  10. title = 'frontend';
  11. httpdata;
  12. ngOnInit() {
  13. this.http.get('http://localhost:8080/cities')
  14. .subscribe((data) => this.displaydata(data));
  15. }
  16. displaydata(data) { this.httpdata = data; }
  17. }

ngOnInit()方法中,我们向后端创建一个 GET 请求。 数据存储在httpdata中。

src/app/app.component.html

  1. <h2>List of cities</h2>
  2. <ul *ngFor = "let data of httpdata">
  3. <li>Name : {{data.name}} Population: {{data.population}}</li>
  4. </ul>

我们使用*ngFor指令在 HTML 列表中显示数据。

  1. $ ng serve

我们启动 Angular 服务器。

  1. $ mvn spring-boot:run

我们运行后端服务器。 现在我们找到localhost:4200。 加载页面后,会将请求发送到 Spring Boot 应用以获取城市列表。

在本教程中,我们为具有 Angular 前端的 Spring Boot 应用启用了 CORS 支持。 由于这两个部分在不同的域上运行,因此需要 CORS。

列出所有 Spring Boot 教程

{% endraw %}