前言

  众所周知,受浏览器同源策略的影响,产生了跨域问题,那么我们应该如何实现跨域呢?本文记录几种跨域的简单实现

  前期准备

  为了方便测试,我们启动两个服务,10086(就是在这篇博客自动生成的项目,请戳:SpringBoot系列——Spring-Data-JPA(究极进化版) 自动生成单表基础增、删、改、查接口)服务提供者,10087服务消费者,消费者有一个页面test.html跟一个后端controller

<!DOCTYPE html>
<!--解决idea thymeleaf 表达式模板报红波浪线-->
<!--suppress ALL -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>跨域测试</title>
<!-- 引入静态资源 -->
<script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
</head>
<body>
<h3>直接ajax请求10086服务的接口</h3>
<button id="button">发送ajax请求</button>
<br/>
<textarea id="text" style="width: 380px; height: 380px;"> </textarea>
</body>
<script th:inline="javascript">
ctx = [[${#request.getContextPath()}]];//应用路径 //绑定按钮点击事件
$("body").on("click","#button",function(e){
$("#text").text("");
//发送ajax请求
$.get("http://localhost:10086/tbUser/get/1",function (data) {
$("#text").text(data);
})
})
</script>
</html>
    @RequestMapping("page/test")
public ModelAndView pageLogin() {
return new ModelAndView("test.html");
}

  我们先启动10086服务

  浏览器访问接口,正常获取数据

  启动10087服务

  访问test.html页面,直接ajax请求10086服务的接口,直接报错

  几种跨域方式

  1、后端安全跨域

  前端发起请求后端,后端使用httpclient(使用方法参考:httpclient+jsoup实现小说线上采集阅读)或者feign(使用方法参考:SpringCloud系列——Feign 服务调用)安全跨域

  我们给10087服务消费者新增两个controller接口,用于后台调用10086跟响应数据,并修改test.html

  httpclient

<!DOCTYPE html>
<!--解决idea thymeleaf 表达式模板报红波浪线-->
<!--suppress ALL -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>跨域测试</title>
<!-- 引入静态资源 -->
<script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
</head>
<body>
<h3>后端安全跨域(httpclient)</h3>
<button id="button">发送ajax请求</button>
<br/>
<textarea id="text" style="width: 380px; height: 380px;"> </textarea>
</body>
<script th:inline="javascript">
ctx = [[${#request.getContextPath()}]];//应用路径
//绑定按钮点击事件
$("body").on("click","#button",function(e){
$("#text").text("");
//发送ajax请求
$.get(ctx + "/test/httpclient",{id:"1"},function (data) {
$("#text").text(data);
})
})
</script>
</html>
    @RequestMapping("/test/httpclient")
public Object httpclient(String id) {
String result = null;
try {
//创建httpclient对象 (这里设置成全局变量,相对于同一个请求session、cookie会跟着携带过去)
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建get方式请求对象
HttpGet httpGet = new HttpGet("http://localhost:10086/tbUser/get/"+id);
httpGet.addHeader("Content-type", "application/json");
//包装一下
httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36");
httpGet.addHeader("Connection", "keep-alive"); //通过请求对象获取响应对象
CloseableHttpResponse response = httpClient.execute(httpGet);
//获取结果实体
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
result = EntityUtils.toString(response.getEntity(), "GBK");
} //释放链接
response.close();
}
//这里还可以捕获超时异常,重新连接抓取
catch (Exception e) {
result = null;
e.printStackTrace();
}
return result;
}

  feign

  我们先在10087服务消费者修改test.html,新增controller接口,maven引入feign,创建TestFeign,值得注意的是消费者的返回值必须与提供者的返回值一致,参数对象也要一致

<!DOCTYPE html>
<!--解决idea thymeleaf 表达式模板报红波浪线-->
<!--suppress ALL -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>跨域测试</title>
<!-- 引入静态资源 -->
<script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
</head>
<body>
<h3>后端安全跨域(feign)</h3>
<button id="button">发送ajax请求</button>
<br/>
<textarea id="text" style="width: 380px; height: 380px;"> </textarea>
</body>
<script th:inline="javascript">
ctx = [[${#request.getContextPath()}]];//应用路径
//绑定按钮点击事件
$("body").on("click","#button",function(e){
$("#text").text("");
//发送ajax请求
$.get(ctx + "/test/feign",{id:"1"},function (data) {
$("#text").text(data);
})
})
</script>
</html>
@FeignClient(name = "http://localhost:10086", path = "/tbUser/")
public interface TestFeign {
@RequestMapping(value = "get/{id}")
Result<UserVo> get(@PathVariable("id") Integer id);
}
    @Autowired
private TestFeign testFeign; @RequestMapping("/test/feign")
public Object feign(Integer id){
return testFeign.get(id);
}

  直接使用url会报这个错,因为我们没有注册这两个服务,eureka也没起...,所以想使用feign调用,两个服务都需要在eureka上注册,负载均衡器才能找到它

  我们将两个服务注册到eureka上(参考springcloud系列博客之SpringCloud系列——Eureka 服务注册与发现),将@FeignClient(name = "http://localhost:10086", path = "/tbUser/")的name的值改成10086服务提供者的 spring.application.name的值即可

  2、jsonp

   由于同源策略,浏览器禁止向不同源的服务器发起请求,但是 HTML 的<script> 元素是一个例外,我们可以利用标签的src发起请求,服务提供者的后端响应消费者期望的数据格式(必须是符合js对象的格式,否则会在回调函数解析的时候报错)

  我们先重写10086服务提供者的get接口,使其返回值符合消费者期待的格式

@RestController
@RequestMapping("/tbUser/")
public class TbUserController extends CommonController<TbUserVo, TbUser, Integer> {
@Autowired
private TbUserService tbUserService; @RequestMapping("get")
public Object get(Integer id, String callback) {
Result<TbUserVo> result = super.get(id);
TbUserVo tbUserVo = result.getData();
StringBuffer re = new StringBuffer();
re.append("{");
re.append("'flag':'" + result.isFlag() + "',");
re.append("'msg':'" + result.getMsg() + "',");
re.append("'data':{");
re.append("'id':'" + tbUserVo.getId() + "',");
re.append("'username':'" + tbUserVo.getUsername() + "',");
re.append("'password':'" + tbUserVo.getPassword() + "',");
re.append("'created':'" + tbUserVo.getCreated() + "',");
re.append("'descriptionId':'" + tbUserVo.getDescriptionId()+"'");
re.append("}}");
return callback + "(" + re.toString() + ")";
}
}

  修改10087服务消费者的test.html

<!DOCTYPE html>
<!--解决idea thymeleaf 表达式模板报红波浪线-->
<!--suppress ALL -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>跨域测试</title>
<!-- 引入静态资源 -->
<script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
</head>
<body>
<h3>jsonp</h3>
<button id="button">发送ajax请求</button>
<br/>
<textarea id="text" style="width: 380px; height: 380px;"> </textarea>
</body>
<script th:inline="javascript">
ctx = [[${#request.getContextPath()}]];//应用路径
//绑定按钮点击事件
$("body").on("click","#button",function(e){
//构造一个<srcipt>标签
$("body").append("<script id='temporaryScript' src=\"http://localhost:10086/tbUser/get?id=1&callback=callback\"><\/script>");
}) //回调
function callback(data) {
$("#text").text("");
$("#text").text(JSON.stringify(data));
//过河拆桥
$("#temporaryScript").remove();
}
</script>
</html>

  

  3、cors

  详情介绍可以看这里:CORS通信 ,代码实现过程参考:Java实现CORS跨域请求

  参考大佬博客,我们在10086服务提供者新建一个CorsFilter过滤器,

@Component
@ServletComponentScan
@WebFilter(filterName = "corsFilter", //过滤器名称
urlPatterns = "/tbUser/*",//拦截路径
initParams = {@WebInitParam(name = "allowOrigin", value = "http://localhost:10087"),//允许来源
@WebInitParam(name = "allowMethods", value = "GET,POST,PUT,DELETE,OPTIONS"),//允许请求方法
@WebInitParam(name = "allowCredentials", value = "true"),
@WebInitParam(name = "allowHeaders", value = "Content-Type,X-Token")})
public class CorsFilter implements Filter { private String allowOrigin;
private String allowMethods;
private String allowCredentials;
private String allowHeaders;
private String exposeHeaders; @Override
public void init(FilterConfig filterConfig) throws ServletException {
allowOrigin = filterConfig.getInitParameter("allowOrigin");
allowMethods = filterConfig.getInitParameter("allowMethods");
allowCredentials = filterConfig.getInitParameter("allowCredentials");
allowHeaders = filterConfig.getInitParameter("allowHeaders");
exposeHeaders = filterConfig.getInitParameter("exposeHeaders");
} @Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (!StringUtils.isEmpty(allowOrigin)) {
if(allowOrigin.equals("*")){
response.setHeader("Access-Control-Allow-Origin", allowOrigin);
}else{
List<String> allowOriginList = Arrays.asList(allowOrigin.split(","));
if (allowOriginList != null && allowOriginList.size() > 0) {
String currentOrigin = request.getHeader("Origin");
if (allowOriginList.contains(currentOrigin)) {
response.setHeader("Access-Control-Allow-Origin", currentOrigin);
}
}
}
}
if (!StringUtils.isEmpty(allowMethods)) {
response.setHeader("Access-Control-Allow-Methods", allowMethods);
}
if (!StringUtils.isEmpty(allowCredentials)) {
response.setHeader("Access-Control-Allow-Credentials", allowCredentials);
}
if (!StringUtils.isEmpty(allowHeaders)) {
response.setHeader("Access-Control-Allow-Headers", allowHeaders);
}
if (!StringUtils.isEmpty(exposeHeaders)) {
response.setHeader("Access-Control-Expose-Headers", exposeHeaders);
}
filterChain.doFilter(servletRequest, servletResponse);
} @Override
public void destroy() { }
}

  然后修改10087服务消费者调用方式,改成普通的ajax请求即可

<!DOCTYPE html>
<!--解决idea thymeleaf 表达式模板报红波浪线-->
<!--suppress ALL -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>跨域测试</title>
<!-- 引入静态资源 -->
<script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
</head>
<body>
<h3>cors</h3>
<button id="button">发送ajax请求</button>
<br/>
<textarea id="text" style="width: 380px; height: 380px;"> </textarea>
</body>
<script th:inline="javascript">
ctx = [[${#request.getContextPath()}]];//应用路径
//绑定按钮点击事件
$("body").on("click","#button",function(e){
//发送ajax请求
$.get("http://localhost:10086/tbUser/get/1",function (data) {
$("#text").text("");
$("#text").text(JSON.stringify(data));
})
})
</script>
</html>

  在启动10086服务提供者时出现一个启动报错,说corsFilter已经存在,叫我们改名或者启用覆盖,我们启用一下覆盖  PS:最好是重命名我们的bean

  启动成功后我们进行测试

  测试下list请求,修改这一部分

        //发送ajax请求
$.post("http://localhost:10086/tbUser/list",{username:"张三"},function (data) {
$("#text").text("");
$("#text").text(JSON.stringify(data));
})

  没有问题!

  以上是CORS是基于Filter过滤器的实现,事实上,springboot通过@CrossOrigin注解优雅的实现CORS跨域,我们在10086服务提供者的controller层那里加入@CrossOrigin注解:

@RestController
@RequestMapping("/tbUser/")
@CrossOrigin(origins = "http://localhost:10087", methods = "GET,POST,PUT,DELETE,OPTIONS", allowedHeaders = "Content-Type,X-Token",allowCredentials = "true")
public class TbUserController extends CommonController<TbUserVo, TbUser, Integer> {
@Autowired
private TbUserService tbUserService; }

  @CrossOrigin注解,官方文档:https://spring.io/blog/2015/06/08/cors-support-in-spring-framework

  1、Spring 4.2之后提供了跨域注解 @CrossOrigin;

  2、可以用在方法或Controller上;

  3、Controller和方法上都有时,Spring会合并两个注解的属性一起使用;

  注解属性有以下7个:

String[] value() default {}
String[] origins() default {} //允许来源
String[] allowedHeaders() default {}
String[] exposedHeaders() default {}
RequestMethod[] methods() default {} //允许调用方法
String allowCredentials() default {}
long maxAge() default -1L

  如果是SpringBoot项目,还有更简洁的配置,我们看一下官网介绍:CORS Support

management.endpoints.web.cors.allowed-origins=http://localhost:10087
management.endpoints.web.cors.allowed-methods=GET,POST,PUT,DELETE,OPTIONS

  后记

  跨域暂时先记录到这里,如果大家发现有什么错误,还望指正

还在问跨域?本文记录js跨域的多种实现实例的更多相关文章

  1. [记录]js跨域调用mvc ActionResult扩展

    背景 最近2个项目中都用到了js跨域访问的知识,2个项目都需要主站与各个分站之间进行数据交互.状态同步等相关操作.浏览器本身是不允许进行跨域访问,在MVC中我们可以扩展一个方法来实现这个功能.在此大家 ...

  2. js获取IP地址多种方法实例教程

    js获取IP地址方法总结   js代码获取IP地址的方法,如何在js中取得客户端的IP地址.原文地址:js获取IP地址的三种方法 http://www.jbxue.com/article/11338. ...

  3. 利用JS跨域做一个简单的页面訪问统计系统

    事实上在大部分互联网web产品中,我们一般会用百度统计或者谷歌统计分析系统,通过在程序中引入特定的JS脚本,然后便能够在这些统计系统中看到自己站点页面详细的訪问情况.可是有些时候,因为一些特殊情况,我 ...

  4. 如何解决js跨域问题

    Js跨域问题是web开发人员最常碰到的一个问题之一.所谓js跨域问题,是指在一个域下的页面中通过js访问另一个不同域下的数据对象,出于安全性考 虑,几乎所有浏览器都不允许这种跨域访问,这就导致在一些a ...

  5. 5种处理js跨域问题方法汇总(转载)

    1.JSONP跨域GET请求 ajax请求,dataType为jsonp.这种形式需要请求在服务端调整为返回callback([json-object])的形式.如果服务端返回的是普通json对象.那 ...

  6. 利用JS跨域做一个简单的页面访问统计系统

    其实在大部分互联网web产品中,我们通常会用百度统计或者谷歌统计分析系统,通过在程序中引入特定的JS脚本,然后便可以在这些统计系统中看到自己网站页面具体的访问情况.但是有些时候,由于一些特殊情况,我们 ...

  7. 解决js跨域问题

    如何解决js跨域问题 Js跨域问题是web开发人员最常碰到的一个问题之一.所谓js跨域问题,是指在一个域下的页面中通过js访问另一个不同域下 的数据对象,出于安全性考 虑,几乎所有浏览器都不允许这种跨 ...

  8. js 跨域问题 汇总

    前言 相信每一个前端er对于跨域这两个字都不会陌生,在实际项目中应用也是比较多的.但跨域方法的多种多样实在让人目不暇接.老规矩,碰到这种情况,就只能自己总结一篇博客,作为记录. 正文 1. 什么是跨域 ...

  9. js跨域解决方式

    什么是跨域? 概念:仅仅要协议.域名.port有不论什么一个不同,都被当作是不同的域.(所谓同源是指,域名.协议,port同样.),对于port和协议的不同,仅仅能通过后台来解决. URL 说明 是否 ...

随机推荐

  1. AbstractQueuedSynchronizer AQS框架源码剖析

    一.引子 Java.util.concurrent包都是Doug Lea写的,来混个眼熟 是的,就是他,提出了JSR166(Java Specification RequestsJava 规范提案), ...

  2. java后台验证码工具

    jcaptcha和kaptcha是两个比较常用的图片验证码生成工具,功能强大.kaptcha是google公司制作,Jcaptcha是CAPTCHA里面的一个比较著名的项目. Shiro 结合 kca ...

  3. 带logo图片或不带logo图片的二维码生成与解析,亲测成功

    最近公司需要实现二维码功能,本人经过一顿百度,终于实现了,因有3个功能:不带logo图片.带logo图片.解析二维码,篇幅较长,请耐心读之,直接复制粘贴即可. 前提:myeclipse10:jar包: ...

  4. JAVA基础第三章-类与对象、抽象类、接口

    业内经常说的一句话是不要重复造轮子,但是有时候,只有自己造一个轮子了,才会深刻明白什么样的轮子适合山路,什么样的轮子适合平地! 我将会持续更新java基础知识,欢迎关注. 往期章节: JAVA基础第一 ...

  5. 如何做好技术Team Leader

    背景 互联网公司的技术团队管理通常分为2个方向:技术管理和团队管理,互联网公司的技术TL与传统软件公司的PM还是有很大的区别,传统软件公司的PM更多注重于对项目的管理包括项目任务拆解.项目进度以及风险 ...

  6. numpy C语言源代码调试(三)

    鉴于ddd过于简陋,希望找一个新一些的调试工具,看到有很多人推荐gdbgui,这是一个非常新的调试工具,前端使用浏览器,现在采用这一架构的软件越来越多,可以完全不必依赖庞大的gui类库,安装使用比较方 ...

  7. Jenkins 集成 SonarQube Scanner

    1.   安装Jenkins 下载安装包,这里我们下载war包 https://jenkins.io/download/ 运行jenkins.war的方式有两种: 第一种:将其放到tomcat中运行( ...

  8. 开发小白也毫无压力的hexo静态博客建站全攻略 - 躺坑后亲诉心路历程

    目录 基本原理 方法1 - 本机Windows下建站 (力荐) 下载安装node.js 用管理员权限打开命令行,安装hexo-cli和hexo 下载安装git 初始化hexo 使用hexo gener ...

  9. Android 8.0对隐式广播的进一步限制

    项目targetSdkVersion升级到26后,对应的的是Android O版本,即Android 8.0系统.经测试发现针对8.0及以上安卓版本手机,AndroidMainfest.xml中静态注 ...

  10. 委托与lambda关系

    什么是委托委托是没有方法体的,声明委托就是一个关键字: delegate ,委托可以试有参无参,有返回值无返回值.和我们的方法是一样的.不同的区别是 委托没有方法体的,委托可放在类下也可以放在类的外面 ...