Java实现后端跨域的常见解决方式

一、搭建服务(cross-server)

1.1、maven依赖

pom.xml

<dependencies>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>

   <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.16.14</version>
   </dependency>
</dependencies>

1.2、接口

@Slf4j
@RestController
@RequestMapping("/cross")
public class CrossController {

    @PostMapping("/request")
    public String crossRequest(HttpServletRequest request) {
        log.info("接收到跨域请求参数:{}",request.getParameter("name"));
        return "Request CrossServer Success";
    }

}

1.3、配置

application.properties

server.port=8080
server.servlet.context-path=/crossServer

  至此我们就提供了一个接口:http://localhost:8080/crossServer/cross/request

二、搭建服务(cross-web)

2.1、maven依赖

pom.xml

<dependencies>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>

   <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.16.14</version>
   </dependency>
</dependencies>

2.2、接口

@Controller
@RequestMapping("/test")
public class WebController {

    @RequestMapping(value = "/request")
    public String request(HttpServletRequest request) {
        return "/cross";
    }
}

2.3、页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>跨域测试页面</title>
    <script src="js/jquery.min.js"></script>
</head>
<body>
<h1>跨域测试</h1>
<div>
    <input type="button" onclick="mySubmit()" value=" 发送跨域请求 ">
</div>
<script>
    function mySubmit() {
        // 发送跨域请求
        jQuery.ajax({
            url: "http://localhost:8086/test",
            type: "POST",
            data: {"name": "Java"},
            success: function (result) {
                alert("返回数据:" + result.data);
            }
        });
    }
</script>
</body>
</html>

2.4、配置

application.properties

server.port=8081
server.servlet.context-path=/crossWeb

spring.freemarker.request-context-attribute=request
spring.freemarker.charset=UTF-8
spring.freemarker.template-loader-path=classpath:/templates/
spring.freemarker.suffix=.ftl
spring.freemarker.prefix=/views

  至此我们就提供了一个接口:http://localhost:8081/crossWeb/test/request,访问此页面可以打开如下测试页面:

在这里插入图片描述

2.5、跨域请求结果

  在没有任何跨域处理的情况下,我们点击按钮,发起请求,得到结果如下:

Access to XMLHttpRequest at 'http://localhost:8080/crossServer/cross/request' from origin 
'http://localhost:8081' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' 
header is present on the requested resource.
POST http://localhost:8080/crossServer/cross/request net::ERR_FAILED 200

2.6、常见跨域情况

请求地址说明是否跨域
http://www.alian.com/a
http://www.alian.com/b
同域名
http://www.alian.com/a/b
http://www.alian.com/c/d
同域名不同文件夹
http://www.alian.com/a
https://www.alian.com/a
同域名不同协议
http://www.alian.com/a
http://www.alian.com:8080/a
同域名下不同端口
http://www.alian.com/a
http://yun.alian.com/a
同顶级域名不同二级域名
http://www.alian.com/a
http://www.yang.com/a
不同域名
http://www.alian.com/a
http://10.130.1.88:80/a
域名和对应的ip

三、解决方案

3.1、通过 @CrossOrigin 注解

  最简单的方式是通过使用注解@CrossOrigin,但是需要注意的是这个注解版本,这个注解是从Spring Framework 4.2 GA开始有的。如果你项目的版本低于这个版本是用不了的。

package com.alian.crossserver.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@Slf4j
@RestController
@RequestMapping("/cross")
public class CrossController {

    @CrossOrigin(origins = "*")
    @PostMapping("/request")
    public String crossRequest(HttpServletRequest request) {
        log.info("接收到跨域请求参数:{}",request.getParameter("name"));
        return "Request CrossServer Success";
    }

}

3.2、通过配置类

  为了避免方法间相互影响,我们先在我们的方法crossRequest,去除注解@CrossOrigin(origins = “*”),后面也是一样就不一一提示了。

package com.alian.crossserver.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**") // 所有接口
                .allowCredentials(true) // 是否发送 Cookie
                .allowedOriginPatterns("*") // 支持域
                .allowedMethods("GET", "POST", "PUT", "DELETE") // 支持方法
                .allowedHeaders("*")
                .exposedHeaders("*");

    }
}

   上面是对全部方法都支持跨域,我们也可以指定某个请求支持跨域,如下:

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/cross/request") // 指定接口
                .allowCredentials(true) // 是否发送 Cookie
                .allowedOriginPatterns("*") // 支持域
                .allowedMethods("GET", "POST", "PUT", "DELETE") // 支持方法
                .allowedHeaders("*")
                .exposedHeaders("*");

    }
}

3.3、通过 CorsFilter 对象

  再没有使用以上方式解决跨域的情况下,我们使用过滤器也可以实现。

package com.alian.crossserver.filter;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CustomCorsFilter {

    @Bean
    public CorsFilter corsFilter() {
        // 创建cors配置对象
        CorsConfiguration config = new CorsConfiguration();
        // 支持域
        config.addAllowedOriginPattern("*");
        // 是否发送 Cookie
        config.setAllowCredentials(true);
        // 支持请求方式
        config.addAllowedMethod("*");
        // 允许的原始请求头部信息
        config.addAllowedHeader("*");
        // 暴露的头部信息
        config.addExposedHeader("*");
        // 添加地址映射
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        corsConfigurationSource.registerCorsConfiguration("/**", config);
        // 返回 CorsFilter 对象
        return new CorsFilter(corsConfigurationSource);
    }
}

  上面是对所有域名请求都支持跨域,我们也可以指定某个域名,意思就是只有指定域名可以跨域请求。比如:我们只允许http://localhost:8081的跨域请求。

@Configuration
public class CustomCorsFilter {

    @Bean
    public CorsFilter corsFilter() {
        // 创建cors配置对象
        CorsConfiguration config = new CorsConfiguration();
        // 支持指定域
        config.addAllowedOriginPattern("http://localhost:8081");
        // 是否发送 Cookie
        config.setAllowCredentials(true);
        // 支持请求方式
        config.addAllowedMethod("*");
        // 允许的原始请求头部信息
        config.addAllowedHeader("*");
        // 暴露的头部信息
        config.addExposedHeader("*");
        // 添加地址映射
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        corsConfigurationSource.registerCorsConfiguration("/**", config);
        // 返回 CorsFilter 对象
        return new CorsFilter(corsConfigurationSource);
    }
}

3.4、通过 Response 对象

  在没有使用以上方式解决跨域的情况下,我们通过Response对象也可以实现跨域。

@Slf4j
@RestController
@RequestMapping("/cross")
public class CrossController {

    @PostMapping("/request")
    public String crossRequest(HttpServletRequest request, HttpServletResponse response) {
        response.setHeader("Access-Control-Allow-Origin", "*");
        log.info("接收到跨域请求参数:{}",request.getParameter("name"));
        return "Request CrossServer Success";
    }

}

  

3.5、通过实现 ResponseBodyAdvice 对象

  如果是嫌上述方法在每个方法里都要设置response.setHeader(“Access-Control-Allow-Origin”, “*”),那就可以统一处理返回。可以利用@ControllerAdvice和ResponseBodyAdvice通知完成。

package com.alian.crossserver.advice;

import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@ControllerAdvice // 织入通知
public class ResponseAdvice implements ResponseBodyAdvice {

    /**
     * 内容是否需要重写(返回true表示重写)
     *
     * @param returnType
     * @param converterType
     * @return
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    /**
     * 放回结果前调用
     *
     * @param body
     * @param returnType
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class selectedConverterType, ServerHttpRequest request,
                                  ServerHttpResponse response) {
        // 设置跨域
        response.getHeaders().set("Access-Control-Allow-Origin", "*");
        return body;
    }

}
  • supports:判断是否要交给 beforeBodyWrite 方法执行,ture:需要;false:不需要
  • beforeBodyWrite:对 response 进行具体的处理

3.6、通过 jsonp 方式

  可能还有些老系统spring版本是Spring Framework 4.2 GA之前的,比如3.X。可能上面有些方式也用不了,比如注解。这里我们可以使用jsonp方式,这里我们先改下我们的前端和后端。

3.6.1、后端

  在之前的控制层增加一个接口jsonpRequest

@Slf4j
@RestController
@RequestMapping("/cross")
public class CrossController {

    @RequestMapping("/jsonpRequest")
    public String jsonpRequest(HttpServletRequest request, HttpServletResponse response,@RequestParam("callback") String callback) {
        log.info("1接收到跨域请求参数:姓名:{},年龄:{}",request.getParameter("name"),request.getParameter("age"));
        log.info("2接收到跨域请求参数:{}",callback);
        String json="{\"msg\":\"Request CrossServer Success\"}";
        return callback+"("+ json +")";
    }
}

  这里需要注意的两个点:

  • 一个是请求的方法得支持GET方法(@RequestMapping就是POST、GET都支持)
  • 返回的结果转为json字符串,作为回调方法的参数
3.6.2、前端方法一

  对应前端第一种方法是jsonp约定俗成的默认值为callback

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test cross</title>
    <script type="text/javascript" src="https://code.jquery.com/jquery-3.0.0.min.js"></script>
</head>
<body>
<h1>跨域接口测试</h1>
<div>
    <input type="button" onclick="crossRequest()" value="发送跨域请求">
    <input type="button" onclick="crossCallBack()" value="老版本发送跨域请求">
</div>
<script>
    function crossRequest() {
        // 发送跨域请求
        jQuery.ajax({
            url: "http://localhost:8080/crossServer/cross/request",
            type: "POST",
            data: {"name": "alian"},
            success: function (result) {
                alert("请求返回数据:" + result);
            }
        });
    }

	// 只看这个方法
    function crossCallBack() {
        // 发送跨域请求
        jQuery.ajax({
            url: "http://localhost:8080/crossServer/cross/jsonpRequest",
            type: "POST",
            data: {"name": "alian","age": "28"},
            dataType: "jsonp", //指定服务器返回的数据类型
            jsonp: "callback",
            success: function (data) {
                alert("success回调:"+JSON.stringify(data));
            }
        });
    }

</script>
</body>
</html>
  • dataType:该参数必须要设置成jsonp
  • jsonp:该参数的值需要与服务器端约定(默认值为callback
  • 成功进入到success

所以我的后端接口就有一个参数接收

@RequestParam("callback") String callback
3.6.3、前端方法二

  假设你不想用默认的方法,比如方法名占用了,怎么办呢?那就像下面这样自定义方法名:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test cross</title>
    <script type="text/javascript" src="https://code.jquery.com/jquery-3.0.0.min.js"></script>
</head>
<body>
<h1>跨域接口测试</h1>
<div>
    <input type="button" onclick="crossRequest()" value="发送跨域请求">
    <input type="button" onclick="crossCallBack()" value="老版本发送跨域请求">
</div>
<script>
    function crossRequest() {
        // 发送跨域请求
        jQuery.ajax({
            url: "http://localhost:8080/crossServer/cross/request",
            type: "POST",
            data: {"name": "alian"},
            success: function (result) {
                alert("请求返回数据:" + result);
            }
        });
    }

	// 看这个方法
    function crossCallBack() {
        // 发送跨域请求
        jQuery.ajax({
            url: "http://localhost:8080/crossServer/cross/jsonpRequest?callback=jsonpCallBack",
            type: "POST",
            data: {"name": "alian","age": "28"},
            dataType: "jsonp", //指定服务器返回的数据类型
            jsonp: "jsonpCallBack",
            success: function (data) {

            }
        });
    }

	// 这个是回调方法了,不会进入到success了
    function jsonpCallBack(data){
        var result = JSON.stringify(data); //json对象转成字符串
        alert("Jsonp请求返回数据:" + result);
    }
</script>
</body>
</html>
  • 首先你得指定你的jsonp方法,比如我这里是jsonpCallBack,也就是回调方法
  • 同时我们请求里需要把回调方法名传递到后台,比如我这里callback=jsonpCallBack
  • 回调就会直接进入到我们的jsonpCallBack方法

  jsonpCallBack是自定义方法名,你也可以改成其他的名字,记得请求参数,请求地址,及其方法定义,三个一起改哦。

  两者的结果如下:

在这里插入图片描述

总结

  跨域是浏览器的行为,跨域问题其实就是浏览器的同源策略所导致的,所以后端就没有跨域一说了。以上几种跨域解决方法单独用一个就行了,尽量不要混用,以免出现其他重复冲突的问题。还有其他一些通过nginx或者网关等方法都可以解决跨域问题,就不一一介绍了。

Java后端应用中,你可以通过配置过滤器(Filter)或使用注解来实现跨域请求的配置。以下是两种常见的方法: 1. 使用过滤器(Filter): 创建一个实现了`javax.servlet.Filter`接口的过滤器类,并在其中设置响应头来允许跨域请求。以下是一个示例: ```java import javax.servlet.*; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) servletResponse; response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); filterChain.doFilter(servletRequest, servletResponse); } // 其他方法 } ``` 在上述代码中,我们在`doFilter`方法中设置了允许所有来源(`*`)的请求访问,并指定了允许的请求方法和请求头。你可以根据需要自定义这些配置。 然后,在你的应用中进行配置,例如使用`web.xml`文件: ```xml <filter> <filter-name>CorsFilter</filter-name> <filter-class>com.example.CorsFilter</filter-class> </filter> <filter-mapping> <filter-name>CorsFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ``` 这样,所有的请求都会经过`CorsFilter`过滤器,从而允许跨域访问。 2. 使用注解: 如果你使用的是Spring框架,可以使用`@CrossOrigin`注解来实现跨域配置。在需要允许跨域请求的控制器方法上添加该注解即可。以下是一个示例: ```java import org.springframework.web.bind.annotation.*; @RestController public class MyController { @CrossOrigin(origins = "*", methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE}) @RequestMapping("/api/example") public String example() { // 处理逻辑 } // 其他方法 } ``` 在上述代码中,我们在`example`方法上添加了`@CrossOrigin`注解,并指定了允许所有来源(`*`)的请求访问,并且允许的请求方法。 请注意,以上示例只是一种常见实现方式,具体的配置方法会根据你使用的框架和服务器环境而有所不同。根据实际情况进行调整和适配。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值