Shiro自定义过滤器报错“No SecurityManager accessible”求助排查
问题排查与解决
问题分析
初始错误原因
将/login/**的过滤器从anon改为自定义customLoginFilter后抛出UnavailableSecurityManagerException,核心原因是:
- 自定义过滤器
CustomLoginFilter错误继承了BasicHttpAuthenticationFilter,该过滤器专门用于HTTP Basic认证,完全不匹配普通登录接口的认证逻辑; - 直接通过
new CustomLoginFilter()创建实例,未将过滤器纳入Spring容器管理,也未注入SecurityManager,导致过滤器执行时无法获取Shiro安全管理器实例。
手动绑定后的401原因
手动绑定SecurityManager后返回401,是因为BasicHttpAuthenticationFilter会自动尝试从请求头的Authorization字段解析Basic认证信息,而你的登录接口是常规表单/JSON参数提交,没有符合要求的认证头,导致认证失败。
解决步骤
1. 更换过滤器基类
将CustomLoginFilter的父类改为AuthenticatingFilter,该类是Shiro专门为自定义用户名密码登录场景设计的基类:
import org.apache.shiro.web.filter.authc.AuthenticatingFilter; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import org.apache.shiro.web.util.WebUtils; import org.apache.commons.io.IOUtils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import java.nio.charset.StandardCharsets; import java.io.IOException; import lombok.extern.slf4j.Slf4j; @Slf4j public class CustomLoginFilter extends AuthenticatingFilter { // 从请求中解析用户名密码,生成认证Token @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { // 示例:解析JSON格式请求体,可根据实际改为表单参数解析 String requestBody = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8); JSONObject params = JSON.parseObject(requestBody); String username = params.getString("username"); String password = params.getString("password"); return new UsernamePasswordToken(username, password); } // 处理登录请求逻辑 @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { if (isLoginRequest(request, response)) { if (isLoginSubmission(request, response)) { // 执行登录认证 return executeLogin(request, response); } return true; } else { // 非登录请求直接返回401 WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED); return false; } } // 登录成功后处理:记录日志+返回响应 @Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { log.info("用户[{}]登录成功", token.getPrincipal()); HttpServletResponse httpResponse = WebUtils.toHttp(response); httpResponse.setStatus(HttpServletResponse.SC_OK); httpResponse.setContentType("application/json;charset=utf-8"); httpResponse.getWriter().write(JSON.toJSONString("登录成功")); return false; // 终止过滤器链,不再执行后续逻辑 } // 登录失败后处理:记录日志+返回错误 @Override protected boolean onLoginFailure(AuthenticationToken token, Exception e, ServletRequest request, ServletResponse response) { log.error("用户[{}]登录失败", token.getPrincipal(), e); HttpServletResponse httpResponse = WebUtils.toHttp(response); httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); httpResponse.setContentType("application/json;charset=utf-8"); try { httpResponse.getWriter().write(JSON.toJSONString("登录失败:" + e.getMessage())); } catch (IOException ex) { ex.printStackTrace(); } return false; } }
2. 修改Shiro配置,注入SecurityManager
将自定义过滤器配置为Spring Bean,并注入SecurityManager,确保过滤器能获取到Shiro安全管理器实例:
// 在ShiroConfig类中添加过滤器Bean定义 @Bean public CustomLoginFilter customLoginFilter(DefaultWebSecurityManager securityManager) { CustomLoginFilter loginFilter = new CustomLoginFilter(); loginFilter.setSecurityManager(securityManager); loginFilter.setLoginUrl("/login/local"); // 指定你的登录接口路径 return loginFilter; } // 修改getShiroFilterBean中的过滤器配置 @Bean public ShiroFilterFactoryBean getShiroFilterBean(DefaultWebSecurityManager defaultWebSecurityManager, CustomLoginFilter customLoginFilter){ ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(defaultWebSecurityManager); LinkedHashMap<String, Filter> filterMap = new LinkedHashMap<>(); filterMap.put("customLoginFilter", customLoginFilter); // 使用Spring容器管理的过滤器实例 bean.setFilters(filterMap); Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/login/**", "customLoginFilter"); bean.setFilterChainDefinitionMap(filterChainDefinitionMap); bean.setLoginUrl("/login/to-login"); return bean; }
3. 验证依赖配置
你的Maven依赖已经适配Spring Boot 3的Jakarta EE规范,无需修改。如果需要简化依赖,可以直接使用Shiro的Jakarta版本starter(版本需匹配):
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.13.0</version> <classifier>jakarta</classifier> </dependency>
关键说明
AuthenticatingFilter是Shiro专门用于自定义登录认证的过滤器基类,比BasicHttpAuthenticationFilter更适配常规登录场景;- 过滤器必须由Spring管理并注入
SecurityManager,Shiro会自动处理ThreadContext的绑定,无需手动操作; - 重写
createToken、onAccessDenied等方法时,需根据实际请求格式(表单/JSON)调整参数解析逻辑。
内容的提问来源于stack exchange,提问作者Nathan
相关产品推荐
相关产品推荐

