最近由于工作需要,需要java的后端api系统,在搭建过程中遇到了一些跨域问题, 在这里做个简单的总结,以便以后可以参考使用。
- 跨域的原因
- 我遇到的跨域问题
- 如何解决跨域问题
- spring Mvc 如何解决跨域
- springSecurity 如何解决跨域
1. 跨域的原因
跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript施加的安全限制。
简单的来说就是浏览器栏上的ip,协议,端口和请求的后端ip,协议,端口不一致问题。
举例来说:
http://127.0.0.1:8080 可以调用http://127.0.0.1:8080/api的接口
http://127.0.0.1:8080 不可以调用https://127.0.0.1:8080/api的接口(协议不同)
http://127.0.0.1:8080 不可以调用http://10.0.10.151:8080/api的接口(IP不同)
http://127.0.0.1:8080 不可以调用http://127.0.0.1:8081/api的接口(端口P不同)`
2.我遇到的跨域问题
后端框架:springBoot + SpringSecurity + session
在搭建过程中发现有一下一些问题:
1.前端提示cors错误
2.前端无法set-cookie
3.前段无法读取到Response headers 中的自定义值
3.如何解决跨域问题
在http协议的response header 中有这样几个属性:
Access-control-Allow-Origin:设置允许访问域
Access-control-Allow-Methods:设置允许访问的方法
Access-control-Allow-Header:设置允许访问的请求头
Access-Control-Expose-Headers:设置允许访问的响应头
所以在reponse headers 中写入以下配置就可以解决上述问题1和问题3.
Access-control-Allow-Origin: *
Access-control-Allow-Methods: POST,GET,OPTIONS,DELETE,PUT
Access-control-Allow-Header: token
Access-Control-Expose-Headers: token
这里的token是自定义的header属性名称,不同的项目属性值不同,没有可以不做设置。
关于上述问题2,在尝试了几种方式后都没有很好的解决,所以就将sessionId信息写入到了自定义的header(token)
中返回给前端
4.spring Mvc 如何解决跨域
如果想在Spring mvc项目中设置上述属性,可以加一个Filter
package cn.web.filter;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CorsFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "token");
response.setHeader("Access-Control-Expose-Headers", "token");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
不要忘记在web.xml中添加filter相关配置
<filter>
<filter-name>crossorigin</filter-name>
<filter-class>cn.web.filter.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>crossorigin</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
5.springSecurity 如何解决跨域
- 方法1:
参考spring Mvc的经验,我们也可以加一个filter
package com.web.filter;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
//@Component
public class MyCorsFilter implements Filter {
// @Override
protected void doFilterInternal(HttpServletRequest servletRequest, HttpServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Expose-Headers", "token");
//Access-Control-Allow-Origin
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, Lamp-App-Token, content-type");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
doFilterInternal((HttpServletRequest)request,(HttpServletResponse)response,chain);
}
@Override
public void destroy() {
Filter.super.destroy();
}
}
将Filter 加入到配置中
package com.cetc.clp.framework.auth;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<MyCorsFilter> someFilterRegistration() {
FilterRegistrationBean<MyCorsFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(sessionFilter());
registration.addUrlPatterns("/*");
registration.setName("MyCorsFilter");
registration.setOrder(Integer.MIN_VALUE + 50);
return registration;
}
@Bean
public MyCorsFilter sessionFilter(){
return new MyCorsFilter();
}
}
-
方法2:
这里还有一种方法是基于spring security框架.
@Override
protected void configure(HttpSecurity http) throws Exception {
...
http.cors().configurationSource(configurationSource());
...
}
@Bean
public CorsConfigurationSource configurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
//config.setAllowCredentials(true);
//config.setAllowedOriginPatterns(Collections.singletonList("*"));
config.addAllowedOrigin("*"); //可以放开上面两行,删除这一行,也可以解决跨域问题
//设置允许访问的方法
config.setAllowedMethods(Arrays.asList("POST", "OPTIONS", "GET", "DELETE", "PUT"));
//设置request header允许设置的属性值
config.addExposedHeader("token");
config.setAllowedHeaders(Arrays.asList("token"));
source.registerCorsConfiguration("/**", config);
return source;
}
总结:网上有很多相关代码来解决跨域问题,其实不管怎么变化(不包括nigix解决域名的方法),最后都是在reponse header中写入上面几个参数。所以在遇到跨域问题时候,可以检查下代码中是否有以上几个参数设置。