前期回顾:让controller没有秘密
1.跨域是啥?
跨域问题是web开发中很经典的一个问题,我们先来重现一下这个问题,让大家能够快速理解(只做重要代码说明)
首先我们来准备两个web项目,两个项目分别在tomcat不同而端口部署,第一个web项目我们只写一个提供一个简单信息的接口,如下:
/**
* @author swing
*/
@Slf4j
@Controller
@RequestMapping("/info")
public class WeatherController {
@GetMapping
@ResponseBody
public String weather() {
return "important information";
}
}
然后我们启动这个项目,将他部署在tomcat的8099端口,接口的信息如下:
URL:http://localhost:8099/info
Response:important information
然后我们回到主项目来,编写如下页面:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Welcome!</title>
<script type="text/javascript">
function change() {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
let divElement = document.getElementById("div1");
divElement.innerHTML = xhr.responseText;
divElement.style.color = "red";
}
};
//记得转码
xhr.open("get", "http://localhost:8099/info", true);
xhr.send();
}
</script>
</head>
<body>
<div id="div1"></div>
<button onclick="change()">Get Info</button>
<h2><a href="http://localhost:8099/info">Get Info</a> </h2>
</body>
</html>
然后我们启动这个项目,将他部署在tomcat的8080端口上,可以看出,我这里分别使用ajax和一个链接标签去请求这个接口,我们来分别看一下运行结果:
点击链接标签中的Get Info 页面成功跳转并获取正确信息:
当点击 按钮进行ajax请求时,翻车了:
这时候就发生了跨域问题,由于浏览器的同源机制,当一个网站试图在自己的域下去请求另外一个域的信息,从而引起这个错误,大家要注意:这里的域和和域名的域还是有区别的,同域(或同源)指的是 域名,子域名,端口,协议 都相同。
那么链接标签<a>是怎么可以请求到接口呢?很明显,他已经直接跳出本域,到达请求目标的域(观察地址栏),因此不涉及跨域问题。
2.如何搞定?
简单了解了跨域问题,我们来说说如何搞定它!
常见的解决方案如下:
2.1.设置接口提供方允许跨域访问
这里我们可以使用一个过滤器,为响应增加一些头部信息:
/**
* @author swing
*/
public class AllowOrigin implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with");
chain.doFilter(request, httpServletResponse);
}
@Override
public void destroy() {
}
}
此时浏览器在接收到该网站的访问后,便不会阻难
2.2.后台请求转发
这个解决方案也很好理解,既然在浏览器中我的请求收到限制,那我为何不找后端帮我代理一下,我想要啥数据跟他说,然后他替我请求,简单给出handler代码:
@GetMapping("info")
@ResponseBody
public String getInfo() {
String httpUrl = "http://localhost:8099/info";
return request(httpUrl, null);
}
/**
* @param httpUrl :请求接口
* @param httpArg :参数
* @return 返回结果
*/
public static String request(String httpUrl, String httpArg) {
BufferedReader reader = null;
String result = null;
StringBuilder sbf = new StringBuilder();
httpUrl = httpUrl + "?" + httpArg;
try {
URL url = new URL(httpUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
InputStream is = connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String strRead = null;
while ((strRead = reader.readLine()) != null) {
sbf.append(strRead);
sbf.append("\r\n");
}
reader.close();
result = sbf.toString();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
2.3.CORS 后台同源配置(SpringMVC提供)(这个不错哟!)
spring为我们提供了一种简便的的方法来解决跨域问题:@CrossOrigin 注解
/**
* @author swing
*/
@Slf4j
@Controller
@RequestMapping("/info")
public class WeatherController {
@CrossOrigin(origins = {}, methods = {}, maxAge = 3600, allowedHeaders = {}, exposedHeaders = {})
@GetMapping
@ResponseBody
public String weather() {
return "important information";
}
}
该注解也可作用在类上(表示作用于该类的所有Handler),其中有几个属性这里说一下:
- origins:允许的跨域访问的站点,相当于 header中的 Access-Control-Allow-Origin 信息,如果设置为" * "则表示所有站点都可以
- methods:即Access-Control-Allow-Methods ,允许的请求方法,GET,POST,PUT,DELETE。
- maxAge:即 Access-Control-Max-Age
浏览器的同源策略,就是出于安全考虑,浏览器会限制从脚本发起的跨域HTTP请求(比如异步请求GET, POST, PUT, DELETE, OPTIONS等等),所以浏览器会向所请求的服务器发起两次请求,第一次是浏览器使用OPTIONS方法发起一个预检请求,第二次才是真正的异步请求,第一次的预检请求获知服务器是否允许该跨域请求:如果允许,才发起第二次真实的请求;如果不允许,则拦截第二次请求。
Access-Control-Max-Age用来指定本次预检请求的有效期,单位为秒,,在此期间不用发出另一条预检请求
- allowedHeaders & exposedHeaders:即 Access-Control-Allow-Headers,表示允许或者排除有某些头部的请求
当然,CORS还支持一个全局配置,在MvcConfig中实现WebMvcConfigurer.addCorsMappings方法即可,如下:
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/info")
.allowedOrigins("http://localhost:8080")
.allowedMethods("PUT", "GET", "POST")
.allowCredentials(true)
.maxAge(3600);
}