原本我的filterChainDefinitionMap是
filterChainDefinitionMap.put("/login/**","anon");
调用接口时响应正常,但现在我有需求。登录成功后需要记录所有"/login/**"
。一些信息,所以我自定义了一个CustomLoginFilter并继承自BasicHttpAuthenticationFilter,重写了onLoginSuccess方法。然后将filterChainDefinitionMap改为filterChainDefinitionMap.put("/login/**", "customLoginFilter");
,此时发现接口调用的返回值为{"timestamp":1713163170942,"status":500,"error":"Internal Server Error","path":"/login/local"}
。查看控制台后,发现错误信息为org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code, either bound to the org.apache.shiro.util.ThreadContext or as a vm static singleton. This is an invalid application configuration.
不知道我的shiro配置是否错误。网上查了资料,其中一种方法是手动将securityManage绑定到当前线程,即
ThreadContext.bind(securityManager);
此时发现接口调用响应的HTTP状态码变成了401,并且没有日志在控制台上生成。
以下是我的shiro配置类。
@Slf4j
@Configuration
public class ShiroConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Value("${redis.biz_cache_time.session}")
private Long sessionExpireTime;
@Autowired
private RedisTemplate<Serializable, Session> redisShiroTemplate;
@Bean
public ShiroFilterFactoryBean getShiroFilterBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(defaultWebSecurityManager);
LinkedHashMap<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("customLoginFilter",new CustomLoginFilter());
bean.setFilters(filterMap);
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/login/**", "customLoginFilter");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
bean.setLoginUrl("/login/to-login");
return bean;
}
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(AbstractAuthenticator abstractAuthenticator){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager());
securityManager.setAuthenticator(abstractAuthenticator);
return securityManager;
}
@Bean("lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AbstractAuthenticator abstractAuthenticator() {
ModularRealmAuthenticator authenticator = new MyCustomModularRealmAuthenticator();
authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
List<Realm> realms = new ArrayList<>();
realms.add(customRealm());
authenticator.setRealms(realms);
return authenticator;
}
@Bean
public DefaultWebSessionManager sessionManager() {
StatelessSessionManager sessionManager = new StatelessSessionManager();
sessionManager.setSessionIdCookie(cookie());
sessionManager.setGlobalSessionTimeout(sessionExpireTime);
sessionManager.setSessionDAO(redisSessionDAO());
sessionManager.setDeleteInvalidSessions(true);
return sessionManager;
}
@Bean
public CustomRealm customRealm() {
return new CustomRealm();
}
@Bean
public SimpleCookie cookie() {
SimpleCookie cookie = new SimpleCookie("Authorization");
cookie.setPath("/");
return cookie;
}
@Bean
SessionDAO redisSessionDAO() {
return new RedisSessionDAO(MagicValueConst.SHIRO_REDIS_PREFIX, sessionExpireTime, redisShiroTemplate);
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
}
我的maven配置如下:
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
...
<properties>
<java.version>17</java.version>
</properties>
...
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.13.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<classifier>jakarta</classifier>
<version>1.13.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<classifier>jakarta</classifier>
<version>1.13.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<classifier>jakarta</classifier>
<version>1.13.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
</exclusions>
</dependency>
看起来这里有很多额外的配置。我的建议是“降低复杂性”,直到一切正常。看一下 Shiro 树中的示例:
https://github.com/apache/shiro/blob/f15073b8f7b6bc38af0575f6442885c945b319ab/samples/spring-boot-3-web/src/main/java/org/apache/shiro/samples/WebApp.java#L86-L92BasicHttpAuthenticationFilter
并设置一个使用 cookie 的无状态会话管理器? 与基本身份验证类似的流程不会从请求特定登录 URL 开始(并且不需要 cookie)。
一般来说,请谨慎覆盖默认配置,因为您可能会绕过一些开箱即用的默认安全设置。您能否在 GitHub(或类似的地方)上创建一个简单的示例并定义重现此问题的步骤?