完美解决跨域问题(基于Springboot,无需前端配置)

  在做前后端分离项目的时候,经常会遇见跨域的问题。上百度或者谷歌可以搜到很多解决方案,但把那些解决方案直接粘到自己的项目里面用的时候会出现各种各样的问题。这些问题的出现很耽误进度。所以我参考这篇文章基于Springboot,自己实现了后端的跨域配置,前端无需设置proxy或者使用nginx之类的东西。

实现思路

所谓跨域是指客户端向服务器发送一个http请求,服务器向客户端发送http响应报文时因为客户端和服务器端处于两个域下(端口号不同或者域名都不同),浏览器认为这个请求是不安全的,所以会直接屏蔽这次请求。因此我们需要让服务器的响应报文告知浏览器这次的跨域请求是安全的,从而使得客户端可以收到响应报文。

需要为响应报文添加的head

  • Access-Control-Allow-Origin 它的值必须要和请求中的origin(客户端的url)相同
  • Access-Control-Allow-Methods 指定Restful api
  • Access-Control-Max-Age 指定存活时间
  • Access-Control-Allow-Headers 指定contentType
  • Access-Control-Allow-Credentials 当客户端设置credential为true时,这个值也必须设为true,否则会影响cookie的发送

具体流程

  1. 检查请求报文的origin与预先设定的origin是否相同,如果相同,则执行步骤2否则认为这是一次非法访问,直接返回
  2. 检查请求报文的头部method是否是Options,如果是Options或者是一次简单请求且未设置过head,则执行步骤3,否则认为头部信息已经设置过了,没有必要重复设置头部信息
  3. 为响应报文添加相应的head

具体代码

整体基于拦截器实现

import org.apache.commons.lang.StringUtils;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 这里是跨域逻辑
 */
public class CorsFilter implements Filter {

    /**
     * 允许调用server http服务的client
     * http://localhost:9528 is true
     * http://localhost:9528/ not true
     */
    private String origin = "http://localhost:9528";

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        if (!isLegalRequest(request)) {
            writeErrorMessage(request, response);
            return;
        }
        handleOptions(request, response);
        filterChain.doFilter(servletRequest, servletResponse);
    }

    /**
     * 处理options方法(http预检)
     *
     * @param request
     * @param response
     */
    private void handleOptions(HttpServletRequest request, HttpServletResponse response) {
        if (!checkIsOptions(request)) {
            //not options method or this request is a simple request and
            //not set head
            if(hasSetResponseHead(response)){
                return;
            }
        }
        //设置Http响应头
        //允许客户端的origin访问服务器端
        response.addHeader("Access-Control-Allow-Origin", origin);
        //设置客户端可以使用的method,使用RestfulAPI
        response.addHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        //设置客户端的存活时间1天,在存活时间内,不必再次触发options方法
        response.addHeader("Access-Control-Max-Age", String.valueOf(86400));
        //设置contentType,其中X-PING为自定义type
        response.addHeader("Access-Control-Allow-Headers", "X-PING");
        if (request.getCookies() != null) {
            //client设置了credential,所以要将Access-Control-Allow-Credentials设为true
            response.addHeader("Access-Control-Allow-Credentials", "true");
        }
        response.addHeader("Content-Length", "0");
        response.addHeader("Content-Type", "text/plain");
    }

    /**
     * 检查请求的合法性
     *
     * @param request
     * @return
     */
    private boolean isLegalRequest(HttpServletRequest request) {
        String accessOrigin = request.getHeader("Origin");
        return StringUtils.equals(origin, accessOrigin);
    }

    private boolean checkIsOptions(HttpServletRequest request) {
        return StringUtils.equals(request.getMethod(), "OPTIONS");
    }

    private boolean hasSetResponseHead(HttpServletResponse response) {
        return StringUtils.equals(response.getHeader("Access-Control-Allow-Origin"),
                origin);
    }

    private void writeErrorMessage(HttpServletRequest request,
                                   HttpServletResponse response) throws IOException {
        response.addHeader("Content-Type", "text/plain");
        PrintWriter writer = response.getWriter();
        writer.print("This HTTP resource is not allowed for "
                + request.getHeader("Origin"));
        writer.close();
        response.flushBuffer();
    }
}

拦截器的注册代码

import com.zacharytse.wangpan.filter.CorsFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class CorsConfig {
    
    @Bean
    public FilterRegistrationBean corsFilter() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        CorsFilter corsFilter = new CorsFilter();
        registrationBean.setFilter(corsFilter);
        List<String> urls = new ArrayList<>();
        urls.add("/*");
        registrationBean.setUrlPatterns(urls);
        return registrationBean;
    }
}

测试接口代码如下

 @GetMapping("/test_cors")
    @ResponseBody
    public String testCors() {
        return "success";
    }

前端使用axios做测试(前端url为http://localhost:9528,也就是请求报文中的origin)

Axios({
  method:'Get',
  url:'http://localhost:9000/test_cors',
  contentType:'application/json;charset=UTF-8'
}).then(function(response){
  console.log(response)
})

发现可以成功访问
在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值