问题来源
web前后端工程的分离,给传统的web一体式开发或前后端工程师揉在一块(前后端代码在一个工程下,尤其前端开发需要在本地启动后台服务,后台也会被前端代码分散注意力)的尴尬情况提出了解决方案,同时也引来了一些不可避免的问题,而跨域问题就是其中之一。
设想一下,前端工程师正在开发登录页面的UI,基于nodejs(或基于nodejs的快速开发工具,如vue-cli),可以很容易的在本地启动一个服务端口用于页面的开发。此时,前端代码需要调用后台的登录接口做测试,于是将参考API文档,拿到了登录接口的相关信息(url=/login,params={})准备调用后台接口测试,这时问题来了,如何远程调用后台接口,肯定不可能只配置一下axios(http工具)的baseUrl就完事,还要找到后台的同学,请他将后台web服务设置允许跨域请求,不然你就会在浏览器控制台收到类似下面的警告:
OPTIONS http://localhost:8088/login 404
Access to XMLHttpRequest at 'http://localhost:8088/login' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
解决方案
1、让前端工程师安装nginx,设置http请求转发,这样可以绕过浏览器跨域警告(不推荐,因为每一个前端开发都需要安装nginx,还需要学习配置nginx,透明度不够)
2、后端服务开启跨域请求设置(推荐,对于前端来说基本是透明的)
spring security如何解决跨域问题
环境说明
- java8
- spring boot 2.2.0.RELEASE
官方说明
通过官方文档可以知道,配置是在spring security中,但是最终还是会交给spring mvc去处理
spring官方说明
绕坑!
最开始以为这是很常见的问题,应该很好解决,于是随便baidu了一下,的确有一大堆相关的案例解决方案,基本思路和代码大概有三种,这里就不详细说了,请自行百度相关问题。
百度出来的结果都大概看了下,也都试过,没有一个可行,不知道是不是我的版本太新的原因。最后只好查看官方文档(看英文有点慢,需要加强啊),意外的是,这次官方给出的说明很简单也很直接(show you the best practice code),但是也没有解决问题。于是再次结合百度出来的结果,然后分析浏览器给出的错误警告内容,将官方代码稍作了调整后,跨域配置才终于生效。
code
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private ActiveProfile activeProfile;
@Override
protected void configure(HttpSecurity http) throws Exception {
//你的其他配置
......
//dev config
if (activeProfile.isDev()) {
//允许跨域请求
// by default uses a Bean by the name of corsConfigurationSource(官方说明,使下面配置的bean生效)
http.cors(Customizer.withDefaults());
}
}
@Profile("dev")
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("*");//修改为添加而不是设置,* 最好改为实际的需要,我这是非生产配置,所以粗暴了一点
configuration.addAllowedMethod("*");//修改为添加而不是设置
configuration.addAllowedHeader("*");//这里很重要,起码需要允许 Access-Control-Allow-Origin
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
还没有结束
上述配置没有问题,到目前为止,服务器端已经完全支持跨域请求了,但是的确还没有结束。应为笔者在测试过程中又遇到了坑,下面来具体谈一谈。
提示:如果你的前端用到了axios,一定注意axios虽然默认的content-type='application/x-www-form-urlencoded'
,但是body中的参数是以json字符串的格式提交的,请使用 URLSearchParams 封装,如下:
const params = new URLSearchParams();
params.append('param1', 'value1');
params.append('param2', 'value2');
axios.post('/foo', params);
回到正题,基于上面的配置,现在登录已经没有了问题,但是在跨域访问api时会发现接口返回302,接口被重定向的原因经过查看spring security的dubg日志发现是因为该接口是匿名访问,也就是说你未登录。但是刚才也的确登录成功了,而且浏览器cookie当中还有我们的sessionid,再仔细观察发现api接口发送未带cookie,这才是接口被解决的真正原因而并非真的没有登录。查看官方对于跨域的说明后,发现跨域访问默认是不会携带cookie的,除非你设置参数告诉浏览器我要带cookie去跨域访问资源,代码如下:
//全局配置,告诉浏览器无论如何都要携带cookie去请求资源
axios.defaults.withCredentials=true
或者
//也可以在单独的请求显示指明 withCredentials=true
self.axios({
method: 'post',
url: '/api/getSomething',
data: params,
withCredentials: true//
}).then(function (rep) {
//your code......
});
如果你使用的是webpack或者基于webpack或nodejs构建的开发工具(如:vue-cli)
你可以通过配置代理,这样的好处是你可以省略对 withCredentials 参数的配置,例:
//vue.config.js
module.exports = {
devServer: {
proxy: 'http://localhost:4000'
}
}
关于该配置的详细说明参考:
http-proxy-middleware