一、为何要提供跨域资源服务
最近在写的一个项目后端使用了Spring Boot和Spring Security,前端使用Vue和axios(发送ajax)。为了实现前后端分离,前端和后端能部署在不同的域名下,后端要开启CORS(跨站资源共享)
,保证后端接口能被不同域名下的前端正常请求。
由于浏览器的同源策略,跨域请求一般会被拒绝,导致ajax无法访问到后端资源。
跨域的几种场景,可以判断自己的网站是否跨域:
- 域名不同
- 域名相同,端口不同
- 域名相同,协议不同,如http和https
了解了为什么要开启CORS后可以开始编写开启CORS的代码了。
二、Spring Boot配置里开启CORS
Spring Boot开启CORS只需在配置里添加CORS的支持,直接编写配置类即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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 { private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); return corsConfiguration; }
@Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", buildConfig()); return new CorsFilter(source); } }
|
三、Spring Security配置
由于使用Spring Security后请求都会被它拦截,需要对security也进行配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ...
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest() .permitAll() .and() .formLogin() .loginProcessingUrl("/user/login") .successHandler(new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); JSONObject result = JSONObject.fromObject(new Result(Result.SUCCESS, "登录成功!")); out.write(result.toString()); out.flush(); out.close(); } }) .failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); JSONObject result = JSONObject.fromObject(new Result(Result.ERROR, "登录失败!")); out.write(result.toString()); out.flush(); out.close(); } }) .usernameParameter("username") .passwordParameter("password") .permitAll() .and() .logout() .logoutUrl("/logout") .permitAll(); http.cors().and().csrf().disable(); }
@Override public void configure(WebSecurity web) { web.ignoring().antMatchers("/css/**", "/js/**", "/js/**", "/lib/**", "/upload/**"); } }
|
这里有两个坑:
设置CORS和CSRF
一是,下面这一句必须要设置,否则请求会直接403 Forbidden
1
| http.cors().and().csrf().disable();
|
自定义登录处理器
二是,SpringSecurity通常配置有http.formLogin().successForwardUrl().failureForwardUrl()
这几个配置,但是这些配置都是基于服务器渲染页面的模式,在前后端分离项目中登录只用发送携带登录信息的ajax,服务器响应结果即可,开启这些跳转配置会使ajax无法正常获取响应。
所以要自定义处理器,登录成功响应对应的Json结果,写到HttpServletResponse
中:
1 2 3 4 5 6 7 8 9 10 11 12
| new AuthenticationSuccessHandler() { @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { httpServletResponse.setContentType("application/json;charset=utf-8"); PrintWriter out = httpServletResponse.getWriter(); JSONObject result = JSONObject.fromObject(new Result(Result.SUCCESS, "登录成功!")); out.write(result.toString()); out.flush(); out.close(); } }
|
四、前端设置
在跨域请求的时候,通常会自动发送两个请求。第一个请求方法为OPTION,目的是探测后端是否支持跨域,如果支持跨域再发送真实请求。这里是自动发送不用自己编写代码。
然后在发送登录请求的时候,会遇到前端ajax发送了username和password参数,但是后端收不到参数而验证失败。这里的原因是,前端ajax比如axios通常以json格式发送数据,请求头中content-type
为application/json
,但是Spring Security的登录不支持这种格式所以找不到参数。
解决方法有两种,一是重写Spring Security的登录Filter,使其支持json类型数据,操作比较复杂网上有很多相关博客这里就不提了。二是在登录时前端设置数据格式为SpringSecurity支持的application/x-www-form-urlencoded
。
五、总结
至此CORS就已经开启,前端就可以愉快地使用ajax访问后端了。