跨域与请求拦截
首先在这里先要跟大家补充几个关于拦截器、过滤器的知识点,同源策略的知识点大家自行查阅文献。
一、拦截器(Interceptor)和过滤器(Filter)的执行顺序和区别
1、Springboot中Filter的配置
public class HttpServletRequestReplacedFilter implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
if(request instanceof HttpServletRequest) {
requestWrapper = new RequestReaderHttpServletRequestWrapper((HttpServletRequest) request);
}
//获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中。
// 在chain.doFiler方法中传递新的request对象
if(requestWrapper == null) {
chain.doFilter(request, response);
} else {
chain.doFilter(requestWrapper, response);
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
@Configuration
public class ConfigurerAdapter {
@Bean
public FilterRegistrationBean httpServletRequestReplacedRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new HttpServletRequestReplacedFilter());
registration.addUrlPatterns("/*");
registration.addInitParameter("paramName", "paramValue");
registration.setName("httpServletRequestReplacedFilter");
registration.setOrder(1);
return registration;
}
}
以上代码是一份关于@RequestBady IO错误 处理方案的过滤器。使用@Configuration
标签是为了让SpringBoot知道这个类是配置类,需要进行注册。在@Configuration
中,声明注解@Bean
相当于在Spring老版本中在配置文件中声明一个Bean。httpServletRequestReplacedRegistration
方法是对HttpServletRequestReplacedFilter
过滤类的注册。
过滤器依赖于servlet容器。在实现上,基于函数回调,它可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的,是用来做一些过滤操作,获取我们想要获取的数据,比如:在Javaweb中,对传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者Controller进行业务逻辑操作。通常用的场景是:在过滤器中修改字符编码CharacterEncodingFilter
、在过滤器中修改HttpServletRequest的一些参数XSSFilter
(自定义过滤器)可以防御跨站脚本攻击。
2、springboot中拦截器的配置
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, Object o) throws Exception {
//获取请求的方式 路径 和 <u>sessionid</u>
System.err.println(httpServletRequest.getMethod().toUpperCase()+"-"+
httpServletRequest.getRequestURI()+"-"+httpServletRequest.getSession().getId());
HttpSession session = httpServletRequest.getSession();
String contextPath=session.getServletContext().getContextPath();
//简单的权限拦截 除login接口以外 其他的接口都需要已经登录
String[] requireAuthPages = new String[]{
"login",
};
String uri = httpServletRequest.getRequestURI();
uri = StringUtils.remove(uri, contextPath+"/");
String page = uri;
if(begingWith(page, requireAuthPages)){
return true;
}
BeUser user = (BeUser) session.getAttribute("user");
//未登录则拦截
if (user == null) {
System.err.println("失败了失败了");
httpServletResponse.reset();
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json;charset=UTF-8");
PrintWriter pw = httpServletResponse.getWriter();
JSONObject res = new JSONObject();
res.put("codeguess",0);
res.put("message","没登录被拦截了哟");
pw.append(res.toString());
return false;
}
return true;
}
private boolean begingWith(String page, String[] requiredAuthPages) {
boolean result = false;
for (String requiredAuthPage : requiredAuthPages) {
if(StringUtils.startsWith(page, requiredAuthPage)) {
result = true;
break;
}
}
return result;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, Object o,ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}
}
有了这个代码还不够,还需要对拦截器进行注册
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Bean
public LoginInterceptor getLoginIntercepter() {
return new LoginInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(getLoginIntercepter())
.addPathPatterns("/**");
}
}
拦截器依赖于web框架,在SpringMVC中就是依赖于SpringMVC框架。在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在service或者一个方法前,调用一个方法,或者在方法后,调用一个方法,比如动态代理就是拦截器的简单实现,在调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在调用方法后打印出字符串,甚至在抛出异常的时候做业务逻辑的操作。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理。
3、两者的顺序
至于两者的区别在上述内容中已经分别进行了解释,对于两者的顺序则是:请求->过滤器->拦截器的preHandle()->控制器->拦截器的postHandle()->视图页面渲染->拦截器的afterCompletion()
二、跨域
@Configuration
public class CORSConfiguration extends WebMvcConfigurerAdapter{
@Override
public void addCorsMappings(CorsRegistry registry) {
//所有请求都允许跨域
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
.allowCredentials(true);// 允许跨域带上cookies;
}
}
请求拦截在上面已经贴出。
export const login = (userName, password) => {
return (dispatch) => {
// console.log('`${baseURL}/login`',`${baseURL}/login`);
fetch(`${Url}/login`,{
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include', // 请求带上cookies,是每次请求保持会话一直
body: JSON.stringify({
userName: userName,
password: password
})
}).then((res) => res.json()).then(data=>{
console.log('loginData',data);
const result = data;
console.log('result.data:',result.data);
if (result.data) {
message.success('登陆成功');
dispatch(changeLogin(result))
}else {
message.error(`${result.message}`);
}
}).catch((err)=>{
console.log(err);
})
}
}
credentials: ‘include’,所有的请求都带上cookie
或者axios设置withCredentials: true
这样就实现了简单的权限拦截,但是因此还是会出现跨域问题,原因是复杂请求会先发出option预请求做嗅探,而option不会带上sessionid,服务器则会新分配一个id给他导致与服务器保存好的id不一致导致这个请求被拦截,出现了跨域问题。
解决方案如下
1、拦截器判断
拦截器截取到请求先进行判断,如果是option请求的话,则放行
if("OPTIONS".equals(httpServletRequest.getMethod().toUpperCase())) {
System.err.println("OP:OK");
return true;
}
2、配置Spring Security,设置不拦截OPTIONS请求
HttpSecurity#authorizeRequests()
.antMatchers(HttpMethod.OPTIONS)
.permitAll()
3、自定义filter进行跨域
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.setAllowCredentials(true);
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);//配置CorsFilter优先级
return bean;
}