spring-mvc第三期:跨域是啥?如何搞定?

前期回顾:让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);
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章