Spring boot中Shiro和Redis集成后,Spring的@cacheble注解无效。
情况1:Spring boot和Redis集成,@cacheble可用,会把缓存数据写入Redis。
在情况1的前提下:
情况2:Spring boot和Shiro集成,然后用Spring cache抽象出cachemanager,作为Shiro的缓存。控制台未报错,Shiro的认证信息会存入Redis,但出现@cacheble注解无效,即@Cacheble注解的方法的返回值未存入Redis。
在情况2的前提下:
情况3:将Shiro的@Configuration注解去掉,即不用Shiro,@Cacheble可用。
Redis配置清单:
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import javax.annotation.Resource;
import java.lang.reflect.Method;
@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {
/**
* 通过Spring进行注入,参数在application.properties进行配置{RedisProperties};
*/
@Resource
private JedisConnectionFactory factory;
@Bean
@Override
public CacheManager cacheManager() {
System.out.println("RedisCacheConfig.cacheManager : 实例化");
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate());
cacheManager.setDefaultExpiration(60 * 30); // 30min
return cacheManager;
}
@Bean
public RedisTemplate<Object, Object> redisTemplate() {
System.out.println("RedisCacheConfig.redisTemplate : 实例化");
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setKeySerializer(new GenericJackson2JsonRedisSerializer());
// redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
/**
* 自定义key.
* 此方法将会根据完全限定类名+方法名+所有参数的值生成唯一的一个key,即使@Cacheable中的value属性一样,key也会不一样。
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
System.out.println("RedisCacheConfig.keyGenerator : 实例化");
return new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... objects) {
// This will generate a unique key of the class name, the method name
// and all method parameters appended.
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName());
sb.append(method.getName());
for (Object obj : objects) {
sb.append(obj.toString());
}
System.out.println("RedisCacheConfig : keyGenerator <== " + sb.toString());
return sb.toString();
}
};
}
@Bean
@Override
public CacheResolver cacheResolver() {
return new SimpleCacheResolver(cacheManager());
}
@Bean
@Override
public CacheErrorHandler errorHandler() {
// 用于捕获从Cache中进行CRUD时的异常的回调处理器。
return new SimpleCacheErrorHandler();
}
缓存配置:
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Set;
@Component
public class SpringCacheManagerWrapper implements CacheManager {
public static final Logger log = LoggerFactory.getLogger(SpringCacheManagerWrapper.class);
@Resource
private org.springframework.cache.CacheManager springCacheManger;
@Override
public <K, V> Cache<K, V> getCache(String name) throws CacheException {
log.debug("getCache: springCacheManger <== " + springCacheManger);
org.springframework.cache.Cache springCache = springCacheManger.getCache(name);
return new SpringCacheWrapper<K, V>(springCache);
}
private class SpringCacheWrapper<K, V> implements Cache<K, V> {
private org.springframework.cache.Cache springCache;
public SpringCacheWrapper(org.springframework.cache.Cache springCache) {
this.springCache = springCache;
}
@Override
public V get(K key) throws CacheException {
Object value = springCache.get(key);
if (value instanceof SimpleValueWrapper) {
return (V) ((SimpleValueWrapper)value).get();
}
return (V) value;
}
@Override
public V put(K key, V value) throws CacheException {
springCache.put(key, value);
return value;
}
@Override
public V remove(K key) throws CacheException {
Object value = get(key);
springCache.evict(key);
return (V) value;
}
@Override
public void clear() throws CacheException {
springCache.clear();
}
@Override
public int size() {
throw new UnsupportedOperationException("invoke spring cache abstract size method not supported");
}
@Override
public Set<K> keys() {
throw new UnsupportedOperationException("invoke spring cache abstract keys method not supported");
}
@Override
public Collection<V> values() {
throw new UnsupportedOperationException("invoke spring cache abstract values method not supported");
}
}
}
Shiro配置:
package me.dengfengdecao.demo.config.shiro;
import me.dengfengdecao.demo.component.cache.SpringCacheManagerWrapper;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.CachingSecurityManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfiguration {
/*
缓存管理器 使用Redis实现
*/
@Resource
private SpringCacheManagerWrapper springCacheManagerWrapper;
@Bean(name="shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
System.out.println("ShiroConfiguration.shiroFilterFactoryBean : shiro实例化");
// 拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
// authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
filterChainDefinitionMap.put("/**", "authc");
filterChainDefinitionMap.put("admin/**", "authc, roles[admin]");
filterChainDefinitionMap.put("docs/**", "authc, perms[document:read]");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* shiro核心安全管理类
* @return
*/
@Bean
public SecurityManager securityManager() {
CachingSecurityManager securityManager = new DefaultWebSecurityManager(myShiroRealm());
System.out.println("ShiroConfiguration.securityManager : 实例化");
// 将myShiroRealm注入到securityManager中
// securityManager.setRealm(myShiroRealm());
// 将缓存对象注入到SecurityManager中
// securityManager.setCacheManager(shiroRedisCacheManager);
securityManager.setCacheManager(springCacheManagerWrapper);
return securityManager;
}
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
// 注入凭证匹配器
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
System.out.println("ShiroConfiguration.myShiroRealm : 实例化");
return myShiroRealm;
}
/**
* 凭证匹配器
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
System.out.println("ShiroConfiguration.hashedCredentialsMatcher : 实例化");
// 加密算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列的次数,比如散列两次,相当于 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
/**
* 开启shiro aop注解支持.使用代理方式;所以需要开启代码支持;<br/>
* 拦截@ RequiresPermissions注释的方法
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
System.out.println("ShiroConfiguration.authorizaionAttributeSourceAdvisor : 实例化");
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
Realm
import me.dengfengdecao.demo.domain.Permission;
import me.dengfengdecao.demo.domain.Role;
import me.dengfengdecao.demo.domain.User;
import me.dengfengdecao.demo.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
import java.util.Iterator;
import java.util.Set;
/**
* 身份校验核心类<br/>
*
* Created by dengfengdecao on 16/10/1.
*/
public class MyShiroRealm extends AuthorizingRealm {
public static final Logger log = LoggerFactory.getLogger(MyShiroRealm.class);
@Resource
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.info("######## 执行Shiro权限认证 ########");
String username = (String) principalCollection.getPrimaryPrincipal();
log.info("doGetAuthorizationInfo: username <== " + username);
// 获取当前用户信息
User user = userService.findByUsername(username);
log.debug("doGetAuthorizationInfo: user <== " + user);
if (user != null) {
// 权限信息对象authorizationInfo,用来存放查出的用户的所有的角色(role)及权限(permission)
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 当前用户的角色集合
Iterator<Role> iterator = user.getRoles().iterator();
while (iterator.hasNext()) {
authorizationInfo.addRole(iterator.next().getRole());
}
// 当前用户的角色对应的所有权限
Set<Role> roles = user.getRoles();
for (Role role : roles) {
Iterator<Permission> iterator1 = role.getPermissions().iterator();
while (iterator1.hasNext())
authorizationInfo.addStringPermission(iterator1.next().getPermission());
}
return authorizationInfo;
}
// 返回null的话,就会导致任何用户访问被拦截的请求时,都会自动跳转到unauthorizedUrl指定的地址
return null;
}
/**
* 用来验证用户身份
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("######## 执行Shiro身份认证 ########");
// 获取用户登录的用户名
String username = (String) authenticationToken.getPrincipal();
log.debug("doGetAuthenticationInfo: 当前用户username <== " + username);
// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
User user = userService.findByUsername(username);
log.debug("doGetAuthenticationInfo: 从数据库查出的用户user <== " + user);
if (user == null) {
return null;
} else {
//UsernamePasswordToken对象用来存放提交的登录信息
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
log.info("doGetAuthenticationInfo: 验证当前Subject时获取到token为:" + token);
// 加密方式
// 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(),
user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName());
return authenticationInfo;
}
}
}
UserServiceImpl
@Service
@Transactional
@CacheConfig(cacheNames = {"user"})
public class UserServiceImpl implements UserService {
public static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
@Resource
private UserDao userDao;
@Cacheable
@Override
public User findUserById(Long id) {
return userDao.findOne(id);
}
@Cacheable
@Override
public User findByUsername(String username) {
return userDao.findByUsername(username);
}
@CacheEvict
@Override
public void delFromCache(Long id) {
log.info("delFromCache: 移除缓存");
}
@Override
public void save(User user) {
userDao.save(user);
}
@Cacheable
@Override
public List<User> getUsers() {
return (List<User>) userDao.findAll();
}
UserController
@Controller
public class UserController {
public static final Logger log = LoggerFactory.getLogger(UserController.class);
@Resource
private UserService userService;
@RequestMapping(value = "user/{userId}", method = RequestMethod.GET)
@ResponseBody
public Object findUserById(Model model, @PathVariable("userId")Long userId) {
log.info("findUserById: userId : " + userId);
User user = userService.findUserById(userId);
return user.toString();
}
@RequestMapping(value = "user", method = RequestMethod.GET)
@ResponseBody
public String findUserByUsername(@RequestParam("username")String username){
log.info("findUserByUsername: 执行");
User user = userService.findByUsername(username);
return user.toString();
}
@RequestMapping(value = "user2", method = RequestMethod.GET)
@ResponseBody
public String findUserByUsername2(@RequestParam("username")String username){
log.info("findUserByUsername2: 执行");
User user = userService.findByUsername(username);
return user.toString();
}
@RequestMapping(value = "user/delete")
@ResponseBody
public String delete(Long id) {
log.info("delete: 执行");
userService.delFromCache(id);
return "success";
}
/**
* 用户查询<br/>
* @ RequiresPermissions 进行权限管理
* @return
*/
@RequestMapping("user-list")
@RequiresPermissions("user:list")
public String userList(ModelMap map){
List<User> users = userService.getUsers();
log.info("userList: users <== " + users);
map.put("users", users);
return "user/user-list";
}
/**
* 用户添加
* @return
*/
@RequestMapping("user-add")
@RequiresPermissions("user:add")
public String userAdd(){
log.info("userAdd: ");
return "user/user-add";
}
/**
* 用户删除
* @return
*/
@RequestMapping("user-del")
@RequiresPermissions("user:del")
public String userDelete(){
log.info("userDelete: ");
return "user/user-del";
}
}
application.properties
########################################################
### HTTP encoding (HttpEncodingProperties)
########################################################
# Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly.
spring.http.encoding.charset=UTF-8
# Enable http encoding support.
spring.http.encoding.enabled=true
# Force the encoding to the configured charset on HTTP requests and responses.
spring.http.encoding.force=true
# Force the encoding to the configured charset on HTTP requests. Defaults to true when "force" has not been specified.
spring.http.encoding.force-request=true
# Force the encoding to the configured charset on HTTP responses.
spring.http.encoding.force-response=true
########################################################
### spring
########################################################
spring.mvc.view.prefix=/WEB-INF/content/
spring.mvc.view.suffix=.jsp
spring.mvc.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
########################################################
### REDIS (RedisProperties)
########################################################
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.pool.max-active=8
spring.redis.pool.max-idle=8
spring.redis.pool.max-wait=-1
spring.redis.pool.min-idle=0
spring.redis.port=6379
########################################################
### Java Persistence Api
########################################################
# Specify the DBMS
spring.jpa.database=MYSQL
# Show or not log for each sql query
spring.jpa.show-sql=true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto=update
# Naming strategy
# spring.jpa.hibernate.naming.strategy=org.hibernate.cfg.ImprovedNamingStrategy
# stripped before adding them to the entity manager
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.properties.hibernate.format_sql=true
# Register OpenEntityManagerInViewInterceptor.
# Binds a JPA EntityManager to the thread for the entire processing of the request.
spring.jpa.open-in-view=true
请大家帮帮分析原因。
多个Advisor加载导致的问题,在自定义Realm中注入的Service声明中加入@Lazy注解即可解决问题。
stackoverflow有答案
http://stackoverflow.com/ques...