spring security下的跨域问题

什么是CORS?

问题来源

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。