​ 最近在做前后端分离的新框架,选用了后端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"));