在通过***(SysUser) SecurityUtils.getSubject().getPrincipal()*** 获取当前登录对象时,**SecurityUtils.getSubject().getPrincipal()**是正确的对象。
当强转时报错com.spring.model.system.SysUser cannot be cast to com.spring.model.system.SysUser。
求解本人猜测是redis对象序列化反序列化导致,因为shiro没有集成redis时是正确的,但是经过测试,不走shiro单独存储读取对象是没有问题的,这块不是太懂,求大神解答
下面是相关的配置代码
redis配置
@Configuration
@EnableCaching
@EnableRedisHttpSession
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public CacheManager cacheManager(RedisTemplate<?, ?> redisTemplate) {
RedisCacheManager manager = new RedisCacheManager(redisTemplate);
manager.setDefaultExpiration(3600);//设置默认过期时间
return manager;
}
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
shiro配置
@Configuration
public class ShiroConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.password}")
private String password;
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/doLogin", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/libs/**", "anon");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
// 配置需要验证登录后访问的链接
filterChainDefinitionMap.put("/**", "authc");
// 从数据库获取
// List<AdminMenu> list = systemService.selectAllMenu();
//
// for (AdminMenu menu : list) {
// filterChainDefinitionMap.put(menu.getMenuUrl(), "authc");
// }
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(1);//散列的次数
return hashedCredentialsMatcher;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
//注入记住我管理器;
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* 身份认证realm; (自定义,账号密码校验;权限等)
*
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
/**
* 配置shiro redisManager
* <p>
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setExpire(1800);// 配置缓存过期时间
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
return redisManager;
}
/**
* cacheManager 缓存 redis实现
* <p>
* 使用的是shiro-redis开源插件
*
* @return
*/
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* <p>
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/**
* Session Manager
* <p>
* 使用的是shiro-redis开源插件
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* cookie对象;
*
* @return
*/
public SimpleCookie rememberMeCookie() {
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//<!-- 记住我cookie生效时间7天 ,单位秒;-->
simpleCookie.setMaxAge(604800);
return simpleCookie;
}
/**
* cookie管理对象;记住我功能
*
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
return cookieRememberMeManager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
不让用 devtools 会浪费很多的时间
我找到一种解决方案, 老规矩先说方案
在 /resource/META-INF
目录下(没有就创建) 添加文件 spring-devtools.propertis
加入代码
restart.include.mapper=/mapper-[\\w-\\.]+jar
restart.include.pagehelper=/pagehelper-[\\w-\\.]+jar
# 因为我项目中引用了 org.crazycake:shiro-redis ,所以要引用下面这个配置
restart.include.shiro=/shiro-[\\w-\\.]+jar
加入后就见证了奇迹
上面三行代码都是, 添加 jar 包到 restart 类加载器中 =
后面是具体的 jar 包名称, 或正则表达式
DevTools 默认会对 IDE 中引入的所有项目使用 restart 类加载器, 对引入的 jar 包使用 base 类加载器, 因此要保证热部署时使用的类加载器一致就好了
是不是使用了springboot的热部署,如果是的话去除热部署就可以了
兄弟,我最近也遇到相同的问题。集成Redis,单独给securityManager设置cacheManager或者sessionManager都是可以的;
或者给cacheManager设置为ehcache,sessionManager设置为Redis也是可以的,唯独cacheManager和sessionManager都采用Redis的时候就会
在SecurityUtils.getSubject().getPrincipal()进行对象强转的时候报错,也怀疑过序列化的问题,
但是单独设置cacheManager为Redis的时候又可以转换,自己也尝试过去直接去redis取缓存,发现是可以取出来的,又感觉序列化没问题。
不知道你解决了没有?
果然是热部署问题,用了spring-boot-devtools 热部署之后才导致类型无法转换,真是坑啊
**spring-boot-devtools **这个插件不知道怎么回事,有冲突
遇见相同的问题,反反复复弄了一晌才找到,确实是dev插件的问题,原因是二者所使用的类加载器不同,看这个https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-devtools.html#using-boot-devtools-customizing-classload
兄弟,我遇到一个类似的问题,但又不完全一样,去掉热部署包还是不行。我使用的是spring boot +shiro+cas+redis的解决方案,我发现加了redis后,不能重定向到我之前的URL,跟踪代码,发现在WebUtils的getSavedRequest方法获取不到SavedRequest对象,导致没法重定向到原来的页面。我跟踪saveRequest方是保存成功的。
public static SavedRequest getSavedRequest(ServletRequest request) {
SavedRequest savedRequest = null;
Subject subject = SecurityUtils.getSubject();
Session session = subject.getSession(false);
if (session != null) {
savedRequest = (SavedRequest) session.getAttribute(SAVED_REQUEST_KEY);这句话获取不到对象。 } return savedRequest; } 如果我恢复为Ehcache一切又正常了。。。实在搞不懂是哪里出问题了。。如果有人遇到又解决了请给我发邮件39530426@qq.com,
我也遇到相同的问题,只能去掉去掉插件了不知道有没有别的方法处理,求解答
我使用笨方法解决了,用反射获取属性,自己写了一个属性;
/**
* 用于redis session 使用了 spring devtools 导致的类型转换异常
* @param redisObj
* @return
*/
public static SysUserEntity convertObjToEntity(Object redisObj) {
SysUserEntity sysUserEntity = new SysUserEntity();
sysUserEntity.setUserId(NumberUtils.toLong(ReflectUtils.getFieldValue(redisObj, SysUserEntity.FIELD_USERID)+"",0));
sysUserEntity.setUsername(ReflectUtils.getFieldValue(redisObj, SysUserEntity.FIELD_USERNAME)+"");
sysUserEntity.setPassword(ReflectUtils.getFieldValue(redisObj, SysUserEntity.FIELD_PASSWORD)+"");
sysUserEntity.setEmail(ReflectUtils.getFieldValue(redisObj, SysUserEntity.FIELD_EMAIL)+"");
sysUserEntity.setMobile(ReflectUtils.getFieldValue(redisObj, SysUserEntity.FIELD_MOBILE)+"");
sysUserEntity.setStatus(NumberUtils.toInt(ReflectUtils.getFieldValue(redisObj, SysUserEntity.FIELD_STATUS)+"",0));
sysUserEntity.setCreateUserId(NumberUtils.toLong(ReflectUtils.getFieldValue(redisObj, SysUserEntity.FIELD_CREATEUSERID)+"",0));
Object dateObj = ReflectUtils.getFieldValue(redisObj, SysUserEntity.FIELD_CREATETIME);
sysUserEntity.setCreateTime(dateObj != null ? (Date) dateObj : null);
return sysUserEntity;
}
try {
user = (SysUserEntity)principals.getPrimaryPrincipal();
} catch (Exception e) {
user = SysUserEntity.convertObjToEntity(principals.getPrimaryPrincipal());
}
try {
user = (SysUserEntity)principals.getPrimaryPrincipal();
} catch (Exception e) {
user = SysUserEntity.convertObjToEntity(principals.getPrimaryPrincipal());
}
您好,您配置的RedisConfig 也没用到啊?您这也没使用json序列化格式进行存储罢?
我配置了sessionmanager后,为什么启动总是报错
package com.fly.feasy.shiro;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.session.mgt.SessionManager;
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.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.apache.shiro.mgt.SecurityManager;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* shiro 的配置类.
*/
@Configuration
public class ShiroConfig {
/**
* 一、在认证中:
* 1.1,将加密算法定义好后扔到 MyShiroRealm中 也就是自己定义的realm中
* 1.2,将MyShiroRealm定义后扔到SecurityManager中。
* 1.3,后期用到session什么的,都被SecurityManager管理
*
* @return
*/
/**
* 二、配置session(用Redis存储)
* 2.1 需要配置session,就需要将sessionManager配置在SecurityManager中。
* 2.2 sessionManager需要交给Redis来管理,所以定义了RedisSessionDAO
* 2.3 RedisSessionDAO中需要配置Redis的信息,所以定义RedisManager
*
* @return
*/
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.password}")
private String password;
/**
* 自定义shiro过滤器.
* @param securityManager securityManager
* @return ShiroFilterFactoryBean
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置secrityMananger
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 设置登录地址,配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/main.html");
// 设置拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 设置静态资源(静态资源要设置在最前面)
filterChainDefinitionMap.put("/layui-v2.4.5/**","anon");
filterChainDefinitionMap.put("/css/**","anon");
filterChainDefinitionMap.put("/js/**","anon");
// authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问;登录,登出
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/logout", "logout");
// 设置静态资源
filterChainDefinitionMap.put("/css/**","anon");
// filterChainDefinitionMap.put("/user/**", "authc");
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 设置加密方式和加密次数.
* 凭证匹配器.
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了).
* @return HashedCredentialsMatcher
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 加密的方式,散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
// 加密的次数,散列的次数,比如散列两次,相当于 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(2);
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
/**
* 注入自定义Realm.
* @return CustomRealm
*/
@Bean
public CustomRealm customRealm() {
CustomRealm customRealm = new CustomRealm();
// 把上面自己定义的凭证匹配器加入,不然token里面的密码不会进行加密
customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return customRealm;
}
//------------------------- redis-session ----------------------
/**
* 配置shiro redisManager
* <p>
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
return redisManager;
}
/**
* cacheManager 缓存 redis实现
* <p>
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisCacheManager redisCacheManager(){
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* <p>
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setExpire(1800);
return redisSessionDAO;
}
/**
* Session Manager
* <p>
* 使用的是shiro-redis开源插件
*/
@Bean
public SessionManager sessionManager() {
MySessionManager mySessionManager = new MySessionManager();
mySessionManager.setSessionDAO(redisSessionDAO());
return mySessionManager;
}
// -------------------- cookie 记住我功能 --------------------------
/**
* cookie对象;
*
* @return
*/
public SimpleCookie rememberMeCookie() {
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//<!-- 记住我cookie生效时间7天 ,单位秒;-->
simpleCookie.setMaxAge(604800);
return simpleCookie;
}
/**
* cookie管理对象;记住我功能
*
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
return cookieRememberMeManager;
}
/**
* 把Realm注入到SecurityManager中.
* @return SecurityManager
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
defaultSecurityManager.setRealm(customRealm());
// 自定义缓存实现 使用redis
defaultSecurityManager.setCacheManager(redisCacheManager());
// 自定义session管理 使用redis
defaultSecurityManager.setSessionManager(sessionManager());
//注入记住我管理器;
defaultSecurityManager.setRememberMeManager(rememberMeManager());
return defaultSecurityManager;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}