SpringBoot 前后端分离 跨域处理的四种方式

689 阅读6分钟

方式一 类上加注解 @CrossOrigin

//实现跨域注解 //origin="*"代表所有域名都可访问 //maxAge Cookie的有效期 单位为秒 负数,则代表为临时Cookie,不会被持久化,Cookie信息保存在浏览器内存中,浏览器关闭Cookie就消失

@RestController
@RequestMapping(value = "/api")
@CrossOrigin(methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS}, allowedHeaders = "*", origins = "*", allowCredentials = "true", maxAge = 3600)
public class TestController {


@GetMapping("test")
 public User finduser(@RequestParam(value="id") Integer id){
        //此处省略相应代码
    }
    
// @CrossOrigin(originPatterns = "*", allowCredentials = "true")
@PostMapping("/hello")
    public User findAaa( @RequestBody String id){
        //此处省略相应代码
    }
}

方式二 方法上加注解 @CrossOrigin

@RestController
@RequestMapping(value = "/api")
public class TestController {


@GetMapping("test")
 public User finduser(@RequestParam(value="id") Integer id){
        //此处省略相应代码
    }


@RequestMapping(value = "/abnormal", method = RequestMethod.POST, produces = {MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE}, consumes = {MediaType.TEXT_PLAIN_VALUE, MediaType.APPLICATION_JSON_UTF8_VALUE})
@CrossOrigin(methods = {RequestMethod.POST}, allowedHeaders = "*", origins = "*", allowCredentials = "true", maxAge = 3600)
    public User findAaa( @RequestBody String id){
        //此处省略相应代码
    }
}

@CrossOrigin源码解析 要求 - Spring4.2+,jdk 1.8+

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrossOrigin {

    String[] DEFAULT_ORIGINS = { "*" };

    String[] DEFAULT_ALLOWED_HEADERS = { "*" };

    boolean DEFAULT_ALLOW_CREDENTIALS = true;

    long DEFAULT_MAX_AGE = 1800;


    /**
     * 同origins属性一样
     */
    @AliasFor("origins")
    String[] value() default {};

    /**
     * 所有支持域的集合,例如"http://domain1.com"。
     * <p>这些值都显示在请求头中的Access-Control-Allow-Origin
     * "*"代表所有域的请求都支持
     * <p>如果没有定义,所有请求的域都支持
     * @see #value
     */
    @AliasFor("value")
    String[] origins() default {};

    /**
     * 允许请求头重的header,默认都支持
     */
    String[] allowedHeaders() default {};

    /**
     * 响应头中允许访问的header,默认为空
     */
    String[] exposedHeaders() default {};

    /**
     * 请求支持的方法,例如"{RequestMethod.GET, RequestMethod.POST}"}。
     * 默认支持RequestMapping中设置的方法
     */
    RequestMethod[] methods() default {};

    /**
     * 是否允许cookie随请求发送,使用时必须指定具体的域
     */
    String allowCredentials() default "";

    /**
     * 预请求的结果的有效期,默认30分钟
     */
    long maxAge() default -1;

}

方式三 实现filter过滤器方式

SpringBoot 方式



@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class})
@ComponentScan(basePackages = {""})
@EnableDiscoveryClient
@EnableFeignClients
@ImportResource("classpath:config/spring/app.xml")
public class ServiceApplication {

   public static void main(String[] args) {
      SpringApplication.run(ServiceApplication.class, args);
   }
   
   
   private CorsConfiguration buildConfig() {
      CorsConfiguration corsConfiguration = new CorsConfiguration();
      corsConfiguration.addAllowedOrigin("*");
      corsConfiguration.addAllowedHeader("*");
      corsConfiguration.addAllowedMethod("*");
      corsConfiguration.setAllowCredentials(true);//不加不能跨域上传文件,
      corsConfiguration.setMaxAge(3600l);
      return corsConfiguration;
   }
   /**
    * 跨域过滤器
    * @return
    */
   @Bean
   public CorsFilter corsFilter() {
      UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
      source.registerCorsConfiguration("/**", buildConfig()); 
      return new CorsFilter(source);
   }
   
   

   static {
      LogHelper.setLogType(LogTypeEnum.Int);
   }
   @Bean
   public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
      PropertySourcesPlaceholderConfigurer c = new PropertySourcesPlaceholderConfigurer();
      c.setIgnoreUnresolvablePlaceholders(true);
      return c;
   }


   @Bean
   public ServletRegistrationBean getServletRegistrationBean() {
      //放入自己的Servlet对象实例
      ServletRegistrationBean bean = new ServletRegistrationBean(new YiBaoPosController());
      bean.addUrlMappings("/api/entrance");
      return bean;}


}

SpringMVC 方式


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 CORSConfig {
    /**
     * 允许跨域调用的过滤器
     */
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        //允许白名单域名进行跨域调用
        config.addAllowedOriginPattern("*");
        //允许跨越发送cookie
        config.setAllowCredentials(true);
        //放行全部原始头信息
        config.addAllowedHeader("*");
        //允许所有请求方法跨域调用
        config.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}




方式四 实现WebMvcConfigurer接口

@Configuration
public class WebConfig implements WebMvcConfigurer {
    /**
     * 添加跨域支持
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 允许跨域访问的路径  '/**'表示应用的所有方法
        registry.addMapping("/**")
            // 允许跨域访问的来源 '*'表示所有域名来源
            .allowedOriginPatterns("*")
            // .allowedOrigins("*") // 允许跨域访问的来源 SpringBoot2.4.0之前的版本
            // 允许跨域请求的方法  '*'表示所有
            .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
            // 是否允许发送cookie true-允许 false-不允许 默认false。对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json,这个值只能设为true
            .allowCredentials(true)
            // 预检间隔时间1小时,单位为秒。指定本次预检请求的有效期,在有效期间,不用发出另一条预检请求。
            // 浏览器发出CORS简单请求,只需要在头信息之中增加一个Origin字段
            // 浏览器发出CORS非简单请求,会在正式通信之前,增加一次OPTIONS查询请求,称为"预检"请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
            .maxAge(3600)
            // 允许跨域请求可携带的header,'*'表所有header头。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定
            .allowedHeaders("*");
    }
}

cookie的跨域

从Chrome51开始,浏览器cookie添加了一个新属性SameSite,以防止 CSRF 攻击和用户跟踪。

SameSite可取值:Strict、Lax、None。

Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。

Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。导航到目标 URL 的 GET 请求仅包括三种情况:链接、预加载请求和 GET 表单。

网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。

使用Spring-Session,可以使用以下配置来设置 cookie 的 SameSite 属性

 <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-core</artifactId>
        </dependency>


        
@Configuration
public class SpringSessionConfiguration {
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        // Strict-严格模式 Lax-松懈模式 None-无
        cookieSerializer.setSameSite("None");
        cookieSerializer.setUseSecureCookie(true);
        return cookieSerializer;
    }

跨域

1.什么是同源策略及其限制内容?

同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

url的组成

同源策略限制内容有:

  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求发送后,结果被浏览器拦截了

但是有三个标签是允许跨域加载资源:

  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>

2.常见跨域场景

当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。常见跨域场景如下图所示:

特别说明两点:

第一:如果是协议和端口造成的跨域问题“前台”是无能为力的。

第二:在跨域问题上,仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”

这里你或许有个疑问:请求跨域了,那么请求到底发出去没有?

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。

填坑指南

Access to XMLHttpRequest at 'xxx.xxx.com/api/xx/test…' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains multiple values 'null, *', but only one is allowed.

踩坑:

image.png

总结:原因为nginx配置及接口程序中,均设置了【Access-Control-Allow-Origin:*】引起的,注释二者其一即可。


server {
  listen       80;
        listen       443;
        server_name  xxx.xxx.com;

        ssl on;
        ssl_certificate /home/xxx_ca/xxx.com_ssl/_.xxx.com_bundle.pem;
        ssl_certificate_key /home/xxx_ca/xxx.com_ssl/_.xxx.com.key;

        access_log  /usr/logs/xxx.xxx_access.log  main;
        error_log   /usr/logs/xxx.xxx_error.log error;
        error_page 403 /xxx_403.html;
        location = /xxx_403.html {
        root /xxxx/www/test;
        add_header Access-Control-Allow-Origin *;
        charset utf-8;
        allow all;
                }

        location /nginx_status{
                 stub_status on;
                # access_log  off;
                # auth_basic "xxx's nginx";
                # auth_basic_user_file /root/nginx-1.2.6/conf/passwd;
                        }


        location ~* \.(gif|jpg|jpeg|png|bmp|js|swf|css|html|htm)?$
                 {
                   proxy_redirect off;
                   proxy_cache cache_one;
                   proxy_cache_valid 200 12h;
                   proxy_cache_valid 304 12h;
                   proxy_cache_valid 301 302 1m;
                   proxy_cache_valid any 1m;
                   proxy_cache_key $host$uri$is_args$args;
                   add_header Nginx-Cache "$upstream_cache_status";
                   proxy_set_header X-Forwarded-For  $remote_addr;
                   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                   proxy_set_header X-Real-IP  $remote_addr;
                   proxy_set_header X-Forwarded-Proto  $scheme;
                   proxy_set_header Host  $host:$server_port;
                   proxy_cookie_path / "/; httponly; secure; SameSite=None";
                   proxy_pass http://xxx.xxx_server;
                   expires 1d;
                 }
        location ~* \.(php|jsp|cgi|aspx)?$
                 {
                   proxy_set_header X-Forwarded-For  $remote_addr;
                   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                   proxy_set_header X-Real-IP  $remote_addr;
                   proxy_set_header X-Forwarded-Proto  $scheme;
                   proxy_set_header Host  $host:$server_port;
                   proxy_cookie_path / "/; httponly; secure; SameSite=None";
                   proxy_pass http://xxx.xxx_server;
                 }

        location / {
                   
                   proxy_next_upstream http_502 http_504 error timeout invalid_header;
                   proxy_redirect off ;
                   proxy_set_header X-Forwarded-For  $remote_addr;
                   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                   proxy_set_header X-Real-IP  $remote_addr;
                   proxy_set_header X-Forwarded-Proto  $scheme;
                   proxy_set_header Host  $host:$server_port;
                   
                   //修改点
                   proxy_cookie_path / "/; httponly; secure; SameSite=None";
                   
                   #add_header Nginx-Cache "$upstream_cache_status";
                   
                   //修改点
                   #add_header Access-Control-Allow-Origin *;
                   proxy_pass http://xxx.xxx_server;
                 }

}