【Shiro】Unsatisfied dependency expressed through method 'securityManager' parameter 3

大家好,我是烤鸭:

      采坑记录,springboot 整合 shiro。

      环境:

       springboot    2.0.5.RELEASE

       shiro-spring    1.4.0

       shiro-redis    3.1.0

1.问题

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'shiroFilter' defined in class path resource [com/test/shiro/ShiroConfig.class]: Unsatisfied dependency expressed through method 'shiroFilter' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name'securityManager' defined in class path resource [com/test/shiro/ShiroConfig.class]: Unsatisfied dependency expressed through method 'securityManager' parameter 3; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.crazycake.shiro.RedisCacheManager' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

2.原因

    按异常信息去百度查结果,效果不理想。同事本地试过是没有问题的,合代码的时候出现了问题。争取少改动代码解决问题。
    发现不太好改,找不到问题在哪。

    代码重现。主要是

    ShiroConfig.java

package com.test.shiro;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

@Slf4j
public class ShiroConfig {
    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private Integer redisPort;
    /*@Value("${spring.redis.password}")
    private String redisPassword;*/
    @Value("${spring.redis.timeout}")
    private Integer redisTimeout;
 
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //默认跳转到登陆页面
        shiroFilterFactoryBean.setLoginUrl("/test/web/sysUser/noLogin");
        //登陆成功后的页面
        shiroFilterFactoryBean.setSuccessUrl("/test/web/sysUser/loginSuccess");
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        //自定义过滤器
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        shiroFilterFactoryBean.setFilters(filterMap);

        // 权限控制map
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        // 配置不会被拦截的链接 顺序判断
        // 配置登录方法不被拦截
        filterChainDefinitionMap.put("/web/login", "anon");
        filterChainDefinitionMap.put("/test/web/login", "anon");
        filterChainDefinitionMap.put("/test/web/sysUser/login", "anon");
        filterChainDefinitionMap.put("/web/sysUser/login", "anon");
        filterChainDefinitionMap.put("/test/web/sysUser/code", "anon");
        filterChainDefinitionMap.put("/test/web/sysUser/verification/code", "anon");
        filterChainDefinitionMap.put("/test/web/sysUser/addSysUser", "anon");
        // 配置APP接口不被拦截
        filterChainDefinitionMap.put("/test/app/**", "anon");
        // 配置静态页面不被拦截
//        filterChainDefinitionMap.put("/static/**", "anon");
        // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");

        // 过滤链定义,从上向下顺序执行,一般将/**放在最为下边。
        // authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
        filterChainDefinitionMap.put("/**", "authc"); // 拦截所有链接

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 核心的安全事务管理器
     * 设置realm、cacheManager等
     */
    @Bean(name = "securityManager")
    public SecurityManager securityManager(ShiroRealm shiroRealm, SessionManager sessionManager, RememberMeManager rememberMeManager, RedisCacheManager cacheManager){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        // 设置 Realm
        securityManager.setRealm(shiroRealm);
        // 记住密码管理器
        securityManager.setRememberMeManager(rememberMeManager);
        // 自定义session管理 使用redis
        securityManager.setSessionManager(sessionManager);
        // 自定义缓存实现 使用redis
        securityManager.stestacheManager(cacheManager);
        return securityManager;
    }

    /**
     * Session的管理
     */
    @Bean(name = "sessionManager")
    public DefaultWebSessionManager sessionManager(RedisSessionDAO redisSessionDAO) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO);
        // 设置session过期时间为1小时(单位:毫秒),默认为30分钟
        sessionManager.setGlobalSessionTimeout(60 * 60 * 1000);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        return sessionManager;
    }

    /**
     * 身份认证Realm,此处的注入不可以缺少。否则会在UserRealm中注入对象会报空指针.
     */
    @Bean(name = "shiroRealm")
    public ShiroRealm shiroRealm(HashedCredentialsMatcher hashedCredentialsMatcher){
        ShiroRealm myShiroRealm = new ShiroRealm();
        myShiroRealm.stestredentialsMatcher(hashedCredentialsMatcher);
        return myShiroRealm;
    }

    /**
     * 哈希密码比较器。在ShiroRealm中作用参数使用
     * 登陆时会比较用户输入的密码,跟数据库密码配合盐值salt解密后是否一致。
     */
    @Bean(name = "hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5"); //散列算法:这里使用md5算法;
        hashedCredentialsMatcher.setHashIterations(2); //散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }

    /**
     * 配置自定义的密码比较器
     */
    @Bean(name = "credentialsMatcher")
    public CredentialsMatcher credentialsMatcher(){
        return new CredentialsMatcher();
    }

    /**
     * 配置shiro redisManager
     * 使用的是shiro-redis开源插件
     */
    @Bean(name = "redisManager")
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(redisHost);
        redisManager.setPort(redisPort);
       // redisManager.setPassword(redisPassword);
        //redisManager.setExpire(1800); // 配置缓存过期时间
        redisManager.setTimeout(redisTimeout);
        return redisManager;
    }

    /**
     * cacheManager 缓存 redis实现
     * 使用的是shiro-redis开源插件
     */
    @Bean(name = "cacheManager")
    public RedisCacheManager cacheManager(RedisManager redisManager) {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager);
        //redisCacheManager.getRedisManager()
        return redisCacheManager;
    }

    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 使用的是shiro-redis开源插件
     */
    @Bean(name = "redisSessionDAO")
    public RedisSessionDAO redisSessionDAO(RedisManager redisManager) {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager);
        return redisSessionDAO;
    }

    /**
     * 记住我管理器
     */
     @Bean(name = "rememberMeManager")
    public CookieRememberMeManager rememberMeManager(Cookie rememberMeCookie) {
         CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
         cookieRememberMeManager.stestookie(rememberMeCookie);
         // rememberMe cookie加密的密钥  默认AES算法
//         cookieRememberMeManager.stestipherKey();
         return  cookieRememberMeManager;
    }

    /**
     * cookie对象
     */
    @Bean(name = "rememberMeCookie")
    public Cookie rememberMeCookie() {
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        // 记住我cookie生效时间,单位:秒
        simpleCookie.setMaxAge(3600);
        return simpleCookie;
    }

    /**
     *  开启shiro aop注解支持.
     *  使用代理方式;所以需要开启代码支持;否则@RequiresRoles等注解无法生效
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * Shiro生命周期处理器
     */
    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 自动创建代理
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargtestlass(true);
        return advisorAutoProxyCreator;
    }
}

根据报错信息和上面的代码找问题。异常都是从下往上看的。
源头:
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.crazycake.shiro.RedisCacheManager' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

RedisCacheManager 没注入,所以用到的地方会报错。
用到的地方是 securityManager(ShiroRealm shiroRealm, SessionManager sessionManager, RememberMeManager rememberMeManager, RedisCacheManager cacheManager) 构造方法的第三个参数
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityManager' defined in class path resource [com/test/shiro/ShiroConfig.class]: Unsatisfied dependency expressed through method 'securityManager' parameter 3。
问题找到就好解决了。
改写构造方法。改后的shiroConfig.java如下。

package com.etc.shiro;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

@Slf4j
@Configuration
public class ShiroConfig {
    @Value("${spring.redis.host}")
    private String redisHost;
    @Value("${spring.redis.port}")
    private Integer redisPort;
    @Value("${spring.redis.password}")
    private String redisPassword;
    @Value("${spring.redis.timeout}")
    private Integer redisTimeout;
 
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //自定义过滤器
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        shiroFilterFactoryBean.setFilters(filterMap);

        // 权限控制map
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        // 配置不会被拦截的链接 顺序判断
        // 配置登录方法不被拦截
        filterChainDefinitionMap.put("/invoke/*", "anon");
        filterChainDefinitionMap.put("/web/login", "anon");
        filterChainDefinitionMap.put("/etc/web/login", "anon");
        filterChainDefinitionMap.put("/etc/web/sysUser/login", "anon");
        filterChainDefinitionMap.put("/web/sysUser/login", "anon");
        filterChainDefinitionMap.put("/web/sysUser/getcode", "anon");
        filterChainDefinitionMap.put("/etc/web/sysUser/verification/code", "anon");
        filterChainDefinitionMap.put("/etc/web/sysUser/addSysUser", "anon");
        // 配置APP接口不被拦截
        filterChainDefinitionMap.put("/etc/app/**", "anon");
        // 配置静态页面不被拦截
        filterChainDefinitionMap.put("/static/**", "anon");
        // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");

        // 过滤链定义,从上向下顺序执行,一般将/**放在最为下边。
        // authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
        //· filterChainDefinitionMap.put("/**", "authc"); // 拦截所有链接
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //默认跳转到登陆页面
        shiroFilterFactoryBean.setLoginUrl("/etc/web/sysUser/noLogin");
        //登陆成功后的页面
        shiroFilterFactoryBean.setSuccessUrl("/etc/web/sysUser/loginSuccess");
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        return shiroFilterFactoryBean;
    }

    /**
     * 核心的安全事务管理器
     * 设置realm、cacheManager等
     */
    @Bean
    public SecurityManager securityManager(ShiroRealm shiroRealm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        // 设置 Realm
        securityManager.setRealm(shiroRealm);
        // 记住密码管理器
        securityManager.setRememberMeManager(rememberMeManager());
        // 自定义session管理 使用redis
        securityManager.setSessionManager(sessionManager());
        // 自定义缓存实现 使用redis
        securityManager.setCacheManager(redisCacheManager());
        return securityManager;
    }

    /**
     * Session的管理
     */
    @Bean(name = "sessionManager")
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        // 设置session过期时间为1小时(单位:毫秒),默认为30分钟
        sessionManager.setGlobalSessionTimeout(60 * 60 * 1000);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        return sessionManager;
    }

    /**
     * 身份认证Realm,此处的注入不可以缺少。否则会在UserRealm中注入对象会报空指针.
     */
    @Bean
    public ShiroRealm shiroRealm(){
        ShiroRealm myShiroRealm = new ShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }

    /**
     * 哈希密码比较器。在ShiroRealm中作用参数使用
     * 登陆时会比较用户输入的密码,跟数据库密码配合盐值salt解密后是否一致。
     */
    @Bean(name = "hashedCredentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5"); //散列算法:这里使用md5算法;
        hashedCredentialsMatcher.setHashIterations(2); //散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }

    /**
     * 配置自定义的密码比较器
     */
    @Bean(name = "credentialsMatcher")
    public CredentialsMatcher credentialsMatcher(){
        return new CredentialsMatcher();
    }

//    /**
//     * Shiro 缓存管理器
//     * 需要注入对应的其它的实体类中: 安全管理器:securityManager
//     * 可见securityManager是整个shiro的核心;
//     */
//    @Bean
//    public EhCacheManager ehCacheManager(){
//        logger.info("------------->ShiroConfiguration.getEhCacheManager()执行");
//        EhCacheManager cacheManager = new EhCacheManager();
//        cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
//        return cacheManager;
//    }

    /**
     * 配置shiro redisManager
     * 使用的是shiro-redis开源插件
     */
    @Bean(name = "redisManager")
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(redisHost);
        redisManager.setPort(redisPort);
        redisManager.setPassword(redisPassword);
        //redisManager.setExpire(1800); // 配置缓存过期时间
        redisManager.setTimeout(redisTimeout);
        return redisManager;
    }

    /**
     * cacheManager 缓存 redis实现
     * 使用的是shiro-redis开源插件
     */
    @Bean
    public RedisCacheManager redisCacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }

    /**
     * RedisSessionDAO shiro sessionDao层的实现 通过redis
     * 使用的是shiro-redis开源插件
     */
    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    /**
     * 记住我管理器
     */
     @Bean
    public CookieRememberMeManager rememberMeManager() {
         CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
         cookieRememberMeManager.setCookie(rememberMeCookie());
         // rememberMe cookie加密的密钥  默认AES算法
//         cookieRememberMeManager.setCipherKey();
         return  cookieRememberMeManager;
    }

    /**
     * cookie对象
     */
    @Bean
    public Cookie rememberMeCookie() {
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        // 记住我cookie生效时间,单位:秒
        simpleCookie.setMaxAge(3600);
        return simpleCookie;
    }

    /**
     *  开启shiro aop注解支持.
     *  使用代理方式;所以需要开启代码支持;否则@RequiresRoles等注解无法生效
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * Shiro生命周期处理器
     */
    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 自动创建代理
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
}

遇到问题时,没有搜索到特别好的解决方式时。
并没有根据报错信息仔细查找问题源头(主要也是因为看不懂),应该优先分析报错信息,这样会解决的比较快。
 

展开阅读全文

没有更多推荐了,返回首页