跨域与拦截器导致跨域失败


首先在这里先要跟大家补充几个关于拦截器、过滤器的知识点,同源策略的知识点大家自行查阅文献。

一、拦截器(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;
 }

猜你喜欢

转载自blog.csdn.net/miswujian/article/details/89114025