最近一个项目的认证是用shiro实现的,以前没有做过spring boot + shiro的整合,所以遇到了一些奇奇怪怪的坑

shiro的整合具体这边就不做阐述了,大概有以下几个类:

  • JwtFilter
  • ShiroConfig
  • JwtRealm
  • JwtToken

这里需要关注的只有前两个类,自定义的filter:JwtFilter,以及Shiro配置类

ShiroConfig.java

 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
 @Bean("securityManager")
public DefaultWebSecurityManager getManager(@Autowired JwtRealm jwtRealm) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

    securityManager.setRealm(jwtRealm);

    DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
    DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
    defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
    subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
    securityManager.setSubjectDAO(subjectDAO);

    return securityManager;
}

@Bean("shiroFilter")
public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {
    ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

    Map<String, Filter> filterMap = new LinkedHashMap<>();
    filterMap.put(SHIRO_JWT_FILTER_NAME, new JwtFilter());
    factoryBean.setFilters(filterMap);

    factoryBean.setSecurityManager(securityManager);

    Map<String, String> filterRuleMap = new LinkedHashMap<>();
    filterRuleMap.put("/auth/token", "anon");
    filterRuleMap.put("/v2/api-docs", "anon");
    filterRuleMap.put("/swagger-resources/**", "anon");
    filterRuleMap.put("/api/**", "anon");
    filterRuleMap.put("/**", SHIRO_JWT_FILTER_NAME);

    factoryBean.setFilterChainDefinitionMap(filterRuleMap);

    return factoryBean;
}

anon失效

因为需要在自定义filter中注入一些属性,所以把JwtFilter交给Spring管理

1
2
3
4
@Bean
public JwtFilter jwtFilter() {
    return new JwtFilter();
}

同时修改shiroFilter中的filterMap设置

1
filterMap.put(SHIRO_JWT_FILTER_NAME, jwtFilter());

但是在修改后,filterChainDefinitionMap中设置为anon的路由规则就失效了,所有的接口请求都会走JwtFilter,而不是先走ShiroFilter。

原因:

在将JwtFilter交给Spring管理后,Spring将其注册到filterChain中了,与ShiroFilter同级,所以即使设置了filter的order,在shiroFilter完了之后也会经过JwtFilter,从而导致认证请求调用链的异常

解决方法:

  • 不要把JwtFilter交由Spring管理,直接new一个实例,交由ShiroFilterFactoryBean管理。这样可以保证JwtFilter和ShiroFilter的父子关系,保证filter调用链的正确性
    • 但是这样的话,在JwtFilter中就不能进行依赖注入,只能获取applicationContext来获取对应的bean,比较麻烦
  • 依然把JwtFilter交由Spring管理,但是设置这个bean不要注册到filter调用链中

第一个方法只需要把filterMap中JwtFilter获取方式改回new,同时将该类中依赖注入的地方改用获取Spring上下文从而获取bean的方式,这个方式比较麻烦。

第二个方法则是通过FilterRegistrationBean取消JwtFilter的自动注册,参考 文档

1
2
3
4
5
6
7
8
@Bean
public FilterRegistrationBean registerJwtFilter(@Autowired JwtFilter jwtFilter) {
    // 设置jwt filter不自动注册到spring管理的监听器中,防止与shiro filter同级,导致该监听器必定执行
    FilterRegistrationBean<JwtFilter> jwtFilterRegister = new FilterRegistrationBean<>(jwtFilter);
    jwtFilterRegister.setEnabled(false);

    return jwtFilterRegister;
}

小结

Spring Boot整合Shiro很方便,但是还是有一些注意点的,甚至有的问题还是与Spring的加载机制有关系