package com.zyd.blog.core.config; import com.zyd.blog.core.shiro.ShiroService; import com.zyd.blog.core.shiro.credentials.RetryLimitCredentialsMatcher; import com.zyd.blog.core.shiro.realm.ShiroRealm; import com.zyd.blog.framework.property.RedisProperties; import com.zyd.blog.framework.property.ShiroProperties; import com.zyd.blog.framework.redis.CustomRedisManager; 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.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.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.MethodInvokingFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.core.annotation.Order; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import java.security.NoSuchAlgorithmException; import java.util.Map; /** * Shiro配置类 * * @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @version 1.0 * @website https://docs.zhyd.me * @date 2018/4/24 14:37 * @since 1.0 */ @Configuration @Order(1) public class ShiroConfig { @Autowired private ShiroService shiroService; @Autowired private RedisProperties redisProperties; @Autowired private ShiroProperties shiroProperties; @Bean(name = "lifecycleBeanPostProcessor") public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 修复UnavailableSecurityManagerException(详见issues#IK7C3) * * @param securityManager * @return */ @Bean public MethodInvokingFactoryBean methodInvokingFactoryBean(SecurityManager securityManager) { MethodInvokingFactoryBean bean = new MethodInvokingFactoryBean(); bean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager"); bean.setArguments(securityManager); return bean; } /** * ShiroFilterFactoryBean 处理拦截资源文件问题。 * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在 * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager * Filter Chain定义说明 * 1、一个URL可以配置多个Filter,使用逗号分隔 * 2、当设置多个过滤器时,全部验证通过,才视为通过 * 3、部分过滤器可指定参数,如perms,roles */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl(shiroProperties.getLoginUrl()); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl(shiroProperties.getSuccessUrl()); // 未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl(shiroProperties.getUnauthorizedUrl()); // 配置数据库中的resource Map filterChainDefinitionMap = shiroService.loadFilterChainDefinitions(); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } @Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); creator.setProxyTargetClass(true); return creator; } @Bean(name = "securityManager") public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm authRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(authRealm); securityManager.setCacheManager(redisCacheManager()); // 自定义session管理 使用redis securityManager.setSessionManager(sessionManager()); // 注入记住我管理器 securityManager.setRememberMeManager(rememberMeManager()); return securityManager; } @Bean(name = "shiroRealm") public ShiroRealm shiroRealm(@Qualifier("credentialsMatcher") RetryLimitCredentialsMatcher matcher) { ShiroRealm shiroRealm = new ShiroRealm(); shiroRealm.setCredentialsMatcher(credentialsMatcher()); return shiroRealm; } /** * 凭证匹配器 * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 * 所以我们需要修改下doGetAuthenticationInfo中的代码; * ) * * @return */ @Bean(name = "credentialsMatcher") public RetryLimitCredentialsMatcher credentialsMatcher() { return new RetryLimitCredentialsMatcher(); } /** * 开启shiro aop注解支持. * 使用代理方式;所以需要开启代码支持; * * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } /** * 配置shiro redisManager * 使用的是shiro-redis开源插件 * * @return */ public RedisManager redisManager() { CustomRedisManager redisManager = new CustomRedisManager(); redisManager.setHost(redisProperties.getHost()); redisManager.setPort(redisProperties.getPort()); redisManager.setDatabase(redisProperties.getDatabase()); redisManager.setExpire(redisProperties.getExpire()); redisManager.setTimeout(redisProperties.getTimeout().getNano() * 1000); redisManager.setPassword(redisProperties.getPassword()); return redisManager; } /** * cacheManager 缓存 redis实现 * 使用的是shiro-redis开源插件 * * @return */ @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; } /** * shiro session的管理 */ @Bean public DefaultWebSessionManager sessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setGlobalSessionTimeout(redisProperties.getExpire() * 1000L); sessionManager.setSessionDAO(redisSessionDAO()); return sessionManager; } /** * cookie对象; * * @return */ public SimpleCookie rememberMeCookie() { // 这个参数是cookie的名称,对应前端的checkbox的name = rememberMe SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); // 记住我cookie生效时间30天 ,单位秒。 注释掉,默认永久不过期 2018-07-15 simpleCookie.setMaxAge(redisProperties.getExpire()); return simpleCookie; } /** * cookie管理对象;记住我功能 * * @return */ public CookieRememberMeManager rememberMeManager() { CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); // 使用自定义的序列化类 cookieRememberMeManager.setSerializer(new MySecSerializer<>()); cookieRememberMeManager.setCookie(rememberMeCookie()); //rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 192 256 位) cookieRememberMeManager.setCipherKey(GenerateCipherKey.generateNewKey()); return cookieRememberMeManager; } /** * 解决 shiro 反序列化漏洞 * * https://blog.csdn.net/qq_34775355/article/details/106643678 */ public static class GenerateCipherKey { /** * 随机生成秘钥,参考org.apache.shiro.crypto.AbstractSymmetricCipherService#generateNewKey(int) * * @return byte[] */ public static byte[] generateNewKey() { KeyGenerator kg; try { kg = KeyGenerator.getInstance("AES"); } catch (NoSuchAlgorithmException var5) { String msg = "Unable to acquire AES algorithm. This is required to function."; throw new IllegalStateException(msg, var5); } // 满足合规应使用256位 kg.init(256); SecretKey key = kg.generateKey(); return key.getEncoded(); } } }