spring-boot+shiro+spring-data-redis 实战
一,根据项目要求需要对spring-mvc+shiro的项目转换成spring-boot+shiro+spring-data-redis+redis集群的平台以便于实现shiro在spring-boot中安全认证+权限控制+全局sission管理
项目结构
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>DC.API_WEB</artifactId> <groupId>com.hlink</groupId> <version>1.0.1</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.hlink.web</groupId> <artifactId>DC.WEB_Emp</artifactId> <properties> <spring-version>5.0.5.RELEAS</spring-version> <shiro-version>1.4.0</shiro-version> <slf4j-version>1.7.3</slf4j-version> <kafka-version>2.11</kafka-version> <java-version>1.8</java-version> <junit-version>4.12</junit-version> <jackson.version>2.9.4</jackson.version> </properties> <dependencies> <dependency> <groupId>com.hlink</groupId> <artifactId>DC.API_Commons</artifactId> <version>1.0.0-compiler</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.22</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.dyuproject.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>1.0.8</version> </dependency> <dependency> <groupId>com.dyuproject.protostuff</groupId> <artifactId>protostuff-runtime</artifactId> <version>1.0.8</version> </dependency> <dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.11.0.GA</version> </dependency> <dependency> <groupId>com.sun.el</groupId> <artifactId>el-ri</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz-jobs</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.1.0.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.4.1.Final</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>2.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> <version>2.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>2.4.2.1-RELEASE</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.0.2.RELEASE</version> <configuration> <!-- 没有该配置,devtools 不生效 --> <fork>true</fork> <addResources>true</addResources> </configuration> </plugin> </plugins> <finalName>web</finalName> </build> </project>
spring-boot风格中spring-bean.xml中的数据源配置被省略,spring-mvc.xml中的前段控制器配置被省略,web.xml配置被省略,但是shiro的配置与spring-data-redis的配置仍需要自己配置,spring-boot推荐的方式是使用@Configuration 注解在该注解类中每个方法中使用@Bean注解的方式,大致参考:
- @Configuration
- public class ShiroConfiguration {
- private final Logger log = LoggerFactory.getLogger(this.getClass());
- @Bean
- public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
- log.info("$--ShiroConfiguration.shirFilter()");
- ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
- // 必须设置 SecurityManager
- shiroFilterFactoryBean.setSecurityManager(securityManager);
- //拦截器.
- Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
- // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
- filterChainDefinitionMap.put("/logout", "logout");
- // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->;
- // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
- filterChainDefinitionMap.put("/validatecodeServlet", "anon");//验证码可以匿名访问
- filterChainDefinitionMap.put("/toPasswordReset", "anon");//重置密码可以匿名访问
- filterChainDefinitionMap.put("/static/**", "anon");
- filterChainDefinitionMap.put("/**", "authc");//authc
- // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
- shiroFilterFactoryBean.setLoginUrl("/login");
- // 登录成功后要跳转的链接
- shiroFilterFactoryBean.setSuccessUrl("/index");
- // 未授权界面;
- shiroFilterFactoryBean.setUnauthorizedUrl("/403");
- //自定义表达验证,校验验证码
- Map<String,Filter> filters = new HashMap<String, Filter>();
- filters.put("authc", new MyFormAuthenticationFilter());
- shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
- shiroFilterFactoryBean.setFilters(filters);
- return shiroFilterFactoryBean;
- }
- @Bean
- public SecurityManager securityManager() {
- DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
- //设置realm.
- securityManager.setRealm(myShiroRealm());
- return securityManager;
- }
- /**
- * 身份认证realm;
- * @return
- */
- @Bean
- public MyShiroRealm myShiroRealm(){
- MyShiroRealm myShiroRealm = new MyShiroRealm();
- myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
- return myShiroRealm;
- }
- /**
- * 凭证匹配器
- * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
- * 所以我们需要修改下doGetAuthenticationInfo中的代码;
- * )
- * @return
- */
- @Bean
- public HashedCredentialsMatcher hashedCredentialsMatcher(){
- HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
- hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
- hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列2次,相当于 md5(md5(""));
- return hashedCredentialsMatcher;
- }
- @Bean
- public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
- AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
- advisor.setSecurityManager(securityManager());
- return advisor;
- }
- }
该配置类实际相当于spring-shiro.xml中的配置,参考:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <description>Shiro的配置</description> <import resource="classpath:spring_shiro_redis.xml"/> <aop:aspectj-autoproxy/> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- Single realm app. If you have multiple realms, use the 'realms' property instead. --> <property name="realm" ref="authRealm"/><!-- 引用自定义的realm --> <!--将缓存管理器,交给安全管理器 --> <property name="cacheManager" ref="shiroSpringCacheManager"/> </bean> <!-- 自定义权限认证 --> <bean id="authRealm" class="com.hlink.DC.shiro.CustomRealm"> <!-- 此处注入书库操作对象,根据用户名查询密码 <property name="userService" ref="null" /> --> <!-- 自定义密码加密算法 --> <property name="credentialsMatcher" ref="passwordMatcher"/> <property name="cacheManager" ref="shiroSpringCacheManager"/> <!-- 打开缓存 --> <property name="cachingEnabled" value="true"/> <!-- 打开身份认证缓存 --> <property name="authenticationCachingEnabled" value="true"/> <!-- 打开授权缓存 --> <property name="authorizationCachingEnabled" value="true"/> <!-- 缓存AuthenticationInfo信息的缓存名称 --> <property name="authenticationCacheName" value="authenticationCache"/> <!-- 缓存AuthorizationInfo信息的缓存名称 --> <property name="authorizationCacheName" value="authorizationCache"/> </bean> <!-- 配置自定义缓存管理器,中引入redis缓存管理器 --> <bean id="shiroSpringCacheManager" class="com.hlink.DC.shiro.ShiroSpringCacheManager"> <property name="redisTemplate" ref="redisTemplate"></property> </bean> <!-- 密碼比較器 --> <bean id="passwordMatcher" class="com.hlink.DC.shiro.CustomCredentialsMatcher"> <property name="cacheManager" ref="redisCacheManager"/> <property name="expire_minute" value="30"/> <property name="passwordHash" ref="passwordHash"/> </bean> <!-- 加密器 --> <bean id="passwordHash" class="com.hlink.DC.shiro.Encrypt"> <!-- 使用MD5 --> <property name="hashAlgorithm" value="md5"/> <!-- 加密5次 --> <property name="hashIterations" value="5"/> </bean> <!-- 记住密码Cookie --> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- cookie的名字 --> <constructor-arg value="rememberMe"/> <property name="httpOnly" value="true"/> <!-- 7天, --> <property name="maxAge" value="604800"/> </bean> <!-- sesisonCookie 设置 --> <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- cookie的名字 --> <constructor-arg value="sessionIdCookie"/> <property name="httpOnly" value="true"/> <!-- 30分钟 单位是秒 --> <property name="maxAge" value="1800"/> </bean> <!-- rememberMe管理器,cipherKey生成见{@code Base64Test.java} --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('5aaC5qKm5oqA5pyvAAAAAA==')}"/> <property name="cookie" ref="rememberMeCookie"/> </bean> <!-- 会话管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!-- 设置全局会话超时时间 半小时 单位是毫秒 --> <property name="globalSessionTimeout" value="1800000"/> <!-- url上带sessionId 默认为true --> <property name="sessionIdUrlRewritingEnabled" value="true"/> <property name="sessionIdCookie" ref="sessionIdCookie"/> <property name="sessionDAO" ref="sessionDAO"/> </bean> <!-- 会话DAO 用于会话的CRUD --> <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"> <!-- Session缓存名字,配置30分钟过期 --> <property name="activeSessionsCacheName" value="activeSessionCache"/> <property name="cacheManager" ref="shiroSpringCacheManager"/> </bean> <!-- Shiro Filter --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 安全管理器 --> <property name="securityManager" ref="securityManager"/> <!-- 默认的登陆访问url --> <property name="loginUrl" value="/login.jsp"/> <!-- 登陆成功后跳转的url --> <property name="successUrl" value="/index.jsp"/> <!-- 没有权限跳转的url --> <property name="unauthorizedUrl" value="/unauth.jsp"/> <property name="filterChainDefinitions"> <value> <!-- anon 不需要认证 authc 需要认证 user 验证通过或RememberMe登录的都可以 --> /store/admin/session = anon /store/admin/logout = anon /store/admin/login = anon /web/validationCode/** = anon /store/admin= anon /** = anon </value> </property> </bean> <!-- 静态注入,相当于调用SecurityUtils.setSecurityManager(securityManager) --> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/> <property name="arguments" ref="securityManager"/> </bean> <!-- 保证实现了Shiro内部lifecycle函数的bean执行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <bean id="exceptionHandler" class="com.hlink.DC.shiro.GlobalExceptionHandler" /> </beans>
shiro的缓存中本人使用spring-data-redis集成Redis集群来做,具体配置:
spring-shiro-redis.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <description>spring-redis-cache配置文件</description> <!-- 加载配置属性文件 按需加载 --> <!-- <context:property-placeholder ignore-unresolvable="true" location="classpath:redis.cluster.properties" /> --> <!-- 配置Cluster --> <bean id="redisClusterConfiguration" class="org.springframework.data.redis.connection.RedisClusterConfiguration"> <property name="maxRedirects" value="3"></property> <!-- 节点配置 --> <property name="clusterNodes"> <set> <bean class="org.springframework.data.redis.connection.RedisClusterNode"> <constructor-arg name="host" value="192.168.50.241"></constructor-arg> <constructor-arg name="port" value="6000"></constructor-arg> </bean> <bean class="org.springframework.data.redis.connection.RedisClusterNode"> <constructor-arg name="host" value="192.168.50.241"></constructor-arg> <constructor-arg name="port" value="6001"></constructor-arg> </bean> <bean class="org.springframework.data.redis.connection.RedisClusterNode"> <constructor-arg name="host" value="192.168.50.241"></constructor-arg> <constructor-arg name="port" value="6002"></constructor-arg> </bean> <bean class="org.springframework.data.redis.connection.RedisClusterNode"> <constructor-arg name="host" value="192.168.50.239 "></constructor-arg> <constructor-arg name="port" value="7000"></constructor-arg> </bean> <bean class="org.springframework.data.redis.connection.RedisClusterNode"> <constructor-arg name="host" value="192.168.50.239"></constructor-arg> <constructor-arg name="port" value="7001"></constructor-arg> </bean> <bean class="org.springframework.data.redis.connection.RedisClusterNode"> <constructor-arg name="host" value="192.168.50.239"></constructor-arg> <constructor-arg name="port" value="7002"></constructor-arg> </bean> </set> </property> </bean> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <!--最大空闲连接数 --> <property name="maxIdle" value="100" /> <property name="maxTotal" value="600" /> <!--初始化连接数 --> <property name="minIdle" value="20" /> <!--对拿到的connection进行validateObject校验 --> <property name="testOnBorrow" value="true" /> <!--在进行returnObject对返回的connection进行validateObject校验 --> <property name="testOnReturn" value="true" /> <!--定时对线程池中空闲的链接进行validateObject校验 --> <property name="testWhileIdle" value="true" /> </bean> <bean id="jeidsConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <constructor-arg ref="redisClusterConfiguration" /> <constructor-arg ref="jedisPoolConfig" /> <property name="password" value="hlink"></property> </bean> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jeidsConnectionFactory" /> <!-- 开启事务 --> <property name="enableTransactionSupport" value="true" /> <!-- 序列化策略 推荐使用StringRedisSerializer --> <!-- <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> --> <!-- <property name="valueSerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> --> <!--<property name="hashKeySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> <property name="hashValueSerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> --> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"></bean> </property> <property name="valueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> </property> <property name="hashKeySerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> </property> <property name="hashValueSerializer"> <bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" /> </property> </bean> <bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager" factory-method="create" c:connection-factory-ref="jeidsConnectionFactory" /> </beans>
shiro中的几个关键组成部分: 加密器+自定义密码比较器+全局异常拦截处理器+shiro缓存管理器,这几部分在上述的shiro集成配置中必须存在,如下:
加密器:
package com.hlink.DC.shiro; import org.apache.shiro.crypto.hash.Md5Hash; public class Encrypt { private int hashIterations = 2; private String hashAlgorithm; public int getHashIterations() { return hashIterations; } public void setHashIterations(int hashIterations) { this.hashIterations = hashIterations; } public String getHashAlgorithm() { return hashAlgorithm; } public void setHashAlgorithm(String hashAlgorithm) { this.hashAlgorithm = hashAlgorithm; } /* * 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据, * 常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”, * 产生的散列值是“21232f297a57a5a743894a0e4a801fc3”, 可以到一些md5解密网站很容易的通过散列值得到密码“admin”, * 即如果直接对密码进行散列相对来说**更容易,此时我们可以加一些只有系统知道的干扰数据, * 如用户名和ID(即盐);这样散列的对象是“密码+用户名+ID”,这样生成的散列值相对来说更难**。 */ // 高强度加密算法,不可逆 public String md5(String password, String salt) { return new Md5Hash(password, salt, hashIterations).toString(); } public String sha(String password, String salt) { return null; } public String hmac(String password, String salt) { return null; } public String encrypt(String password, String salt) { if (hashAlgorithm != null && hashAlgorithm.equals("md5") || hashAlgorithm.equals("MD5")) { return md5(password, salt); } /* else if(hashAlgorithm.equals("sha") || hashAlgorithm.equals("SHA")){ return null; } else if(hashAlgorithm.equals("hmac") || hashAlgorithm.equals("HMAC")){ return null; }*/ else{ return null; } } }
自定义密码比较器:
package com.hlink.DC.shiro; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; public class CustomCredentialsMatcher extends SimpleCredentialsMatcher { private org.springframework.cache.CacheManager cacheManager; private int expire_minute = 30; private Encrypt passwordHash; public Encrypt getPasswordHash() { return passwordHash; } public void setPasswordHash(Encrypt passwordHash) { this.passwordHash = passwordHash; } public int getExpire_minute() { return expire_minute; } public void setExpire_minute(int expire_minute) { this.expire_minute = expire_minute; } public org.springframework.cache.CacheManager getCacheManager() { return cacheManager; } public void setCacheManager(org.springframework.cache.CacheManager cacheManager) { this.cacheManager = cacheManager; } public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { UsernamePasswordToken usertoken = (UsernamePasswordToken) token; // 注意token.getPassword()拿到的是一个char[],不能直接用toString(),它底层实现不是我们想的直接字符串,只能强转 Object tokenCredentials = passwordHash.encrypt(String.valueOf(usertoken.getPassword()), usertoken.getUsername()); Object accountCredentials = getCredentials(info); // 将密码加密与系统加密后的密码校验,内容一致就返回true,不一致就返回false System.out.println("用户输入密码:"+tokenCredentials); System.out.println("系統保存密碼: "+getCredentials(info)); return equals(tokenCredentials, accountCredentials); } }
全局异常拦截:
package com.hlink.DC.shiro; import net.sf.json.JSONObject; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @ControllerAdvice public class GlobalExceptionHandler implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { JSONObject js = new JSONObject(); if (e instanceof UnknownAccountException) { js.put("code", "4006"); js.put("msg", "用户名不存在·"); } else if (e instanceof IncorrectCredentialsException) { js.put("code", "4006"); js.put("msg", "用户名或密码错误"); } else if (e instanceof NullPointerException) { js.put("code", "4006"); js.put("msg", "系統空指針異常"); } else if (e instanceof ClassCastException) { js.put("code", "4006"); js.put("msg", "系统类型转换异常"); } else{ js.put("code", "404"); js.put("msg", e.getMessage()); } ModelAndView mv = new ModelAndView(); mv.addObject(js); return mv; } @ExceptionHandler(value = Exception.class) @ResponseBody// 返回json数据 public JSONObject jsonErrorHandler(HttpServletRequest req, Exception e){ JSONObject js = new JSONObject(); if (e instanceof UnknownAccountException) { js.put("code", "4006"); js.put("msg", "用户名不存在·"); } else if (e instanceof IncorrectCredentialsException) { js.put("code", "4006"); js.put("msg", "用户名密码错误"); } else if (e instanceof NullPointerException) { js.put("code", "4006"); js.put("msg", "空指針異常"); } else if (e instanceof ClassCastException) { js.put("code", "4006"); js.put("msg", "类型转换异常"); } else{ js.put("code", "404"); js.put("msg", e.getMessage()); } return js; } }
shiro缓存管理器:
package com.hlink.DC.shiro; import com.hlink.DC_DB.model.User; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.Destroyable; import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.RedisTemplate; import java.util.Collection; import java.util.Set; /** * <p> * 自定义cacheManage 扩张shiro里面的缓存 使用reids作缓存 * </p> * <description> 引入自己定义的CacheManager 关于CacheManager的配置文件在spring-redis-cache.xml中 * </description> * * @author xxxx * @date 2018年2月3日 * @time 14:01:53 */ public class ShiroSpringCacheManager implements CacheManager, Destroyable { private String cacheKeyPrefix = "shiro_login:"; private RedisTemplate<String, Object> redisTemplate; public String getCacheKeyPrefix() { return cacheKeyPrefix; } public void setCacheKeyPrefix(String cacheKeyPrefix) { this.cacheKeyPrefix = cacheKeyPrefix; } public RedisTemplate<String, Object> getRedisTemplate() { return redisTemplate; } public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } @Override public <K, V> Cache<K, V> getCache(String name) throws CacheException { return new ShiroRedisCache<K, V>(cacheKeyPrefix + name); } @Override public void destroy() throws Exception { redisTemplate = null; } public class ShiroRedisCache<K, V> implements Cache<K, V> { private String cacheKey; public ShiroRedisCache(String cacheKey) { this.cacheKey = cacheKey; } @Override public V get(K key) throws CacheException { BoundHashOperations<String, K, V> hash = redisTemplate.boundHashOps(cacheKey); Object k = hashKey(key); return hash.get(k); } @SuppressWarnings("unchecked") @Override public V put(K key, V value) throws CacheException { BoundHashOperations<String, K, V> hash = redisTemplate.boundHashOps(cacheKey); Object k = hashKey(key); hash.put((K) k, value); return value; } @Override public V remove(K key) throws CacheException { BoundHashOperations<String, K, V> hash = redisTemplate.boundHashOps(cacheKey); Object k = hashKey(key); V value = hash.get(k); hash.delete(k); return value; } @Override public void clear() throws CacheException { redisTemplate.delete(cacheKey); } @Override public int size() { BoundHashOperations<String, K, V> hash = redisTemplate.boundHashOps(cacheKey); return hash.size().intValue(); } @Override public Set<K> keys() { BoundHashOperations<String, K, V> hash = redisTemplate.boundHashOps(cacheKey); return hash.keys(); } @Override public Collection<V> values() { BoundHashOperations<String, K, V> hash = redisTemplate.boundHashOps(cacheKey); return hash.values(); } protected Object hashKey(K key) { if (key instanceof PrincipalCollection) {// 此处很重要,如果key是登录凭证,那么这是访问用户的授权缓存;将登录凭证转为user对象,返回user的id属性做为hash // key,否则会以user对象做为hash key,这样就不好清除指定用户的缓存了 PrincipalCollection pc = (PrincipalCollection) key; User user = (User) pc.getPrimaryPrincipal(); return user.getId(); } return key; } } }
最后在贴一下我的启动类:
package com.hlink.DC; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ImportResource; import org.springframework.transaction.annotation.EnableTransactionManagement; @EnableAutoConfiguration @EnableTransactionManagement @ComponentScan @ImportResource(locations = {"classpath:applicationContext.xml", "classpath:spring_shiro_redis.xml", "classpath:spring-shiro.xml", "classpath:spring-dubbo.xml"}) public class DCApplication { public static void main(String[] args) { SpringApplication.run(DCApplication.class, args); } }
spring-boot对市场上常见的框架并不是完全集成的,例如dubbo等,但它的配置加载却具有相当的灵活性,开发者仍可以在开始接触的过渡期间使用自己的配置文件(applicationContext.xml,spring-shiro.xml.spring-dubbo.xml等)只需要在启动类中引入加载即可使用,但切记springboot已经完美的兼容了springmvc,所以不要再多余配置spring-mvc.xml中的所有配置
后言:以上内容均属个人感悟,如有错误欢迎指正,谢谢!!