方式一 类上加注解 @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地址,也非同源。
同源策略限制内容有:
- 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.
踩坑:
总结:原因为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;
}
}