搭建springboot-shiro-vue框架时遇到的坑

​ 最近在做前后端分离的新框架,选用了后端springboot+shiro,前端vue+elementUI,第一次搭SSM之外的非demo项目,尤其shiro更是之前从未接触,折腾了很多天,遇到很多问题,大部分能百度出来,剩下的非常费时的问题且称之为坑吧.

# 跨域

​ 一大部分问题就是跨域造成的,本身vue-cli搭建的项目可以用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 1. axios的baseURL设置为/api
// 2. 如下设置
proxyTable: {
      '/api': {
        target: 'http://127.0.0.1:8080/',
        changeOrigin: true,
        pathRewrite: {
          '^/api': '/'
        }
      }
    },

来解决开发环境跨域的问题,生产环境反正可以打包静态文件到springboot项目中直接跑.

而我还是脑抽得选择了强行跨域,假装自己要把静态文件单独放个服务器跑…为此遇到很多问题,折腾好几天.

因此强力推荐上面的方案,简单快捷地解决跨域!

如果生产环境真的需要跨域的话,再按下面方法设置

大部分跨域的配置都能百度搜出来:

  1. axios要 withCredentials: true

  2. 用session而不用啥特殊的token之类的话,就不用 config.headers['X-Token'] = getToken() ,这句是vueAdmin-template 中带的,需要删掉.

  3. 后端需要配置允许跨域

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    @Configuration
    public class CorsConfig {
        private CorsConfiguration buildConfig() {
            CorsConfiguration corsConfiguration = new CorsConfiguration();
            corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址
            corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
            corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
            corsConfiguration.setAllowCredentials(true);
            return corsConfiguration;
        }
    
        @Bean
        public CorsFilter corsFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.registerCorsConfiguration("/**", buildConfig()); // 4 对接口配置跨域设置
            return new CorsFilter(source);
        }
    }
    

# shiro拦截ajax返回json

​ shiro拦截到需要登录而用户尚未的请求时,会重定向至 /login/auth (未配置时是login.jsp),而ajax是不允许重定向的,ajax会收到302错误码,报错

Failed to load http://localhost:8080/test: Redirect from ‘http://localhost:8080/test’ to ‘http://localhost:8080/login/auth’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:9528’ is therefore not allowed access.

因此我们需要对拦截器进行改造,不要它默认的重定向了,我们直接去response里把json写好输出给前端.

自定义filter,(因为我的后端全部返回json,所以这里不判断是否ajax了)

 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
public class AjaxPermissionsAuthorizationFilter extends FormAuthenticationFilter {

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("returnMsg", "未登录或登录已失效");
        if (this.isLoginRequest(request, response)) {
            if (this.isLoginSubmission(request, response)) {
                return this.executeLogin(request, response);
            } else {
                return true;
            }
        } else {
            PrintWriter out = null;
            HttpServletResponse res = (HttpServletResponse) response;
           	//下面这几行也是后面要讲的坑, 这里的Access-Control-Allow-Origin 设置为*的话,前端还是会报错. 
            res.setHeader("Access-Control-Allow-Origin", "http://localhost:9528");
            //  response1.setHeader("Access-Control-Allow-Origin", "*");
            res.setHeader("Access-Control-Allow-Credentials", "true");
            try {
                res.setCharacterEncoding("UTF-8");//设置编码
                res.setContentType("application/json");//设置返回类型
                out = response.getWriter();
                out.println(jsonObject);//输出
            } catch (Exception e) {
            } finally {
                if (null != out) {
                    out.flush();
                    out.close();
                }
            }
            return false;
        }
    }
}

接下来就是最坑的地方了,拦截器的注入.

原本搜到的方法是在ShiroConfiguration类中注入.

1
2
3
4
5
6
7
8
9
	@Bean(name = "ajaxPermissionsAuthorizationFilter")
    public AjaxPermissionsAuthorizationFilter ajaxPermissionsAuthorizationFilter(){
     return new AjaxPermissionsAuthorizationFilter();
    }

		//然后shiroFilterFactoryBean设置过滤器
  		Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("authc", new AjaxPermissionsAuthorizationFilter());
        shiroFilterFactoryBean.setFilters(filterMap);

结果自定义的拦截器把所有的请求都拦截了,直接无视了我设置的

1
2
3
4
5
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/login/**", "anon");
filterChainDefinitionMap.put("/error", "anon");
filterChainDefinitionMap.put("/**", "authc");

各种百度,能搜到的相关的资料也就只有segmentfault的提问没解决,题主给我的回复也不能解决 , 百度知道提问的没解决 , 简书方案并不能解决 .

最终在某个百度结果的第三页找到这篇博客 ,博主对问题一步一步的排查分析,debug源码,最终知道

Springboot 先加载了我们自定义的 Filter,然后再加载了 ShiroFilter

解决方法: 在自定义的filter里加上下面的代码

1
2
3
4
5
6
@Bean
public FilterRegistrationBean registration(自定义Filter filter) {
    FilterRegistrationBean registration = new FilterRegistrationBean(filter);
    registration.setEnabled(false);
    return registration;
}

在configration类里就不需要声明这个bean,只需要直接调用 filterMap.put("authc", new 自定义Filter());

# Access-Control-Allow-Origin

在上面的自定义filter里,

1
 res.setHeader("Access-Control-Allow-Origin", "http://localhost:9528");

如果设置为*的话,前端虽然可以收到json,但还是会报错

Failed to load http://localhost:8080/test: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’. Origin ‘http://localhost:9528’ is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

不允许设置为通配符* .

虽然此处设置了"http://localhost:9528" 但是这种做法终究不合适.

百度继续搜到此博客

得到解决方法

1
res.setHeader("Access-Control-Allow-Origin", ((HttpServletRequest) request).getHeader("Origin"));