之前搞好了忘记记录下来,shiro要用servlet,所以我没把spring boot2.x切到webflux,切到webflux就会报错
1、先添加shiro依赖
org.apache.shiro
shiro-spring
1.4.0
2、redis依赖
redis.clients
jedis
3.0.0-m1
org.springframework.boot
spring-boot-starter-data-redis
3、一系列的shiro配置文件
我自己定义的yml文件配置,用来注到shiro里面设置session过期时间
# 自定义shiro相关的参数
shiro:
globalSessionTimeout: 3600000 # session生命周期 单位ms 暂设1小时
(1)Realm文件
import com.dream.common.enums.DeleteStatusEnum;
import com.dream.common.model.entity.Employee;
import com.dream.common.model.entity.User;
import com.dream.common.model.vo.user.UserVO;
import com.dream.common.utils.EncryptionUtil;
import com.dream.user.restservice.service.user.service.IUserService;
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.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
public class MyShiroRealm extends AuthorizingRealm {
private static org.slf4j.Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
// @Autowired
// private SysRoleService roleService;
@Autowired
IUserService userService;
/**
* 认证信息.(身份验证) : Authentication 是用来验证用户身份
* @param authcToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
logger.info("---------------- 执行 Shiro 凭证认证 ----------------------");
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
String name = token.getUsername();
String password = String.valueOf(token.getPassword());
// 验证用户是否存在
User user = userService.login(name,password);
if (user != null) {
// 用户为禁用状态
if (user.getIsDelete() == null || user.getIsDelete() == DeleteStatusEnum.DELETE.getCode()) {
throw new DisabledAccountException();
}
logger.info("---------------- Shiro 凭证认证成功 ----------------------");
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user,userVO);
userVO.setUserName(user.getEmployeeName());
//重新赋值token中的密码为加密后的密码
token.setPassword(EncryptionUtil.createPassword(password, user.getSalt()).toCharArray());
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userVO, //设置整个userVO对象作为shiro标识,存储对象时,对象实现Serializable接口,否则shiro会报错
user.getPassword(), //密码
getName() //realm name
);
return authenticationInfo;
}
throw new UnknownAccountException();
}
/**
* 授权验证(验证注解授权、jsp标签授权等等)
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("---------------- 执行 Shiro 权限获取 ---------------------");
Object principal = principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
if (principal instanceof User) {
User userLogin = (User) principal;
//角色
// Set roles = roleService.findRoleNameByUserId(userLogin.getId());
// authorizationInfo.addRoles(roles);
//权限
// Set permissions = userService.findPermissionsByUserId(userLogin.getId());
// authorizationInfo.addStringPermissions(permissions);
}
logger.info("---- 获取到以下权限 ----");
logger.info(authorizationInfo.getStringPermissions().toString());
logger.info("---------------- Shiro 权限获取成功 ----------------------");
return authorizationInfo;
}
}
(2)ShiroConfig
import com.dream.common.constants.ServiceConstants;
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.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author April.Chen
*/
@Configuration
public class ShiroConfig {
@Bean
public RedisSessionDAO getRedisSessionDao() {
RedisSessionDAO sessionDAO = new RedisSessionDAO();
return sessionDAO;
}
/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
*
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
/**
* Shiro生命周期处理器
* @return
*/
@Bean
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* redis缓存管理
* @return
*/
@Bean
public RedisCacheManager redisCacheManager() {
return new RedisCacheManager();
}
/**
* sessionIdCookie的实现,用于重写覆盖容器默认的JSESSIONID
* @return
*/
@Bean
public SimpleCookie sessionIdCookie() {
SimpleCookie simpleCookie = new SimpleCookie();
//cookie的name,对应的默认是 JSESSIONID
simpleCookie.setName(ServiceConstants.SHIRO_SESSION_COOKIES);
simpleCookie.setMaxAge(-1);//设置浏览器关闭才删除cookie
simpleCookie.setPath("/");
simpleCookie.setHttpOnly(true);//只支持http
return simpleCookie;
}
/**
* shiro的session管理
* @return
*/
@Bean
@ConfigurationProperties(prefix="shiro") //从配置文件注入globalSessionTimeout属性
public SessionManager sessionManager() {
//因为@Bean执行比@Value快,为了先注入@Value,只能把@Value作为函数的参数声明了
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(getRedisSessionDao());//自定义redis的sessionDao
//sessionManager.setGlobalSessionTimeout(getSessionTimeout(1800)*1000);//设置全局session超时时间 ms
//sessionManager.setCacheManager(redisCacheManager());
sessionManager.setSessionIdCookieEnabled(true);//启用自定义的SessionIdCookie
sessionManager.setSessionIdCookie(sessionIdCookie());//自定义SessionIdCookie
sessionManager.setSessionIdUrlRewritingEnabled(false);//关闭URL中带上JSESSIONID
sessionManager.setSessionValidationSchedulerEnabled(true);//定时检查失效的session
sessionManager.setDeleteInvalidSessions(true);//启用删除无效sessioin
return sessionManager;
}
/**
* shiro的安全管理
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());//自定义Realm
securityManager.setSessionManager(sessionManager());//自定义session管理,使用redis
securityManager.setCacheManager(redisCacheManager());//自定义缓存时间,使用redis
return securityManager;
}
/**
* 使授权注解起作用
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(securityManager());
return new AuthorizationAttributeSourceAdvisor();
}
/**
* 默认授权所用配置
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
daap.setProxyTargetClass(true);
return daap;
}
// @Bean
// public FilterRegistrationBean delegatingFilterProxy(){
// FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
// DelegatingFilterProxy proxy = new DelegatingFilterProxy();
// proxy.setTargetFilterLifecycle(true);
// proxy.setTargetBeanName("shiroFilter");
// filterRegistrationBean.setFilter(proxy);
// return filterRegistrationBean;
// }
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
shiroFilterFactoryBean.setLoginUrl("/user/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
Map filterChainDefinitionMap = new HashMap<>();
filterChainDefinitionMap.put("/sa/**", "authc");
filterChainDefinitionMap.put("/**", "anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
}
(3)redisSessionDAO是用来实现shiro的SessionDAO类,把shiro的session管理方法接到redis里面去
import com.dream.common.constants.RedisConstants;
import com.dream.common.utils.SerializeUtil;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.ValidatingSession;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.apache.shiro.session.mgt.eis.CachingSessionDAO;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* redis实现共享session
*/
@Component
public class RedisSessionDAO extends AbstractSessionDAO {
private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);
// session 在redis过期时间是30分钟30*60
@Value("${shiro.globalSessionTimeout}")
private Integer expireTime;//设置30分钟
private static String prefix = RedisConstants.REDIS_SHIRO_SESSION;
RedisSessionDAO() {
super();
}
@Autowired
private RedisTemplate redisTemplate;
// 创建session,保存到数据库
@Override
protected Serializable doCreate(Session session) {
Serializable sessionId = generateSessionId(session);
assignSessionId(session, sessionId);
//Serializable sessionId = super.doCreate(session);
logger.debug("创建session:{}", session.getId());
try {
String key = prefix + sessionId.toString();
redisTemplate.opsForValue().set(key, session,expireTime,TimeUnit.MILLISECONDS);//毫秒
} catch (Exception e) {
logger.error("redis获取session异常:"+e.getMessage(),e);
}
return sessionId;
}
/**
* 更新session
* @param session
* @throws UnknownSessionException
*/
@Override
public void update(Session session) throws UnknownSessionException {
logger.debug("更新session:{}", session.getId());
try {
//如果会话过期/停止 没必要再更新了
//这里很重要,不然导致每次接口调用完毕后,由于会话结束,导致更新了空的session到redis了。导致源码老报错session不存在
if (session instanceof ValidatingSession && !((ValidatingSession) session).isValid()) {
return;
}
String key = prefix + session.getId().toString();
redisTemplate.opsForValue().set(key, session,expireTime,TimeUnit.MILLISECONDS);
} catch (Exception e) {
logger.error("redis更新session异常:"+e.getMessage(),e);
}
}
/**
* 删除session
* @param session
*/
@Override
public void delete(Session session) {
logger.debug("删除session:{}", session.getId());
try {
String key = prefix + session.getId().toString();
redisTemplate.delete(key);
} catch (Exception e) {
logger.error("redis删除session异常:"+e.getMessage(),e);
}
}
@Override
public Collection getActiveSessions() {
logger.info("获取存活的session");
Set sessions = new HashSet<>();
Set keys = redisTemplate.keys(prefix+"*");
if (keys != null && keys.size() > 0) {
for (String key : keys) {
Session session = (Session) redisTemplate.opsForValue().get(key);
sessions.add(session);
}
}
return sessions;
}
// 获取session
@Override
protected Session doReadSession(Serializable sessionId) {
logger.debug("获取session:{}", sessionId.toString());
Session session = null;
try {
String key = prefix + sessionId.toString();
session = (Session) redisTemplate.opsForValue().get(key);
if(session!=null){
// session没过期,则刷新过期时间
redisTemplate.boundValueOps(key).expire(expireTime, TimeUnit.MILLISECONDS);//毫秒
}
} catch (Exception e) {
logger.error("redis获取session异常:"+e.getMessage(),e);
}
return session;
}
}
(4)redis管理类,提供给shiro指定缓存管理类的
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.apache.shiro.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import javax.annotation.Resource;
public class RedisCacheManager implements CacheManager {
@Resource
private RedisTemplate
(5)redis的操作类
import com.dream.common.constants.RedisConstants;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class RedisCache implements Cache {
private static final String REDIS_SHIRO_CACHE = RedisConstants.REDIS_SHIRO_CACHE;
private String cacheKey;
private RedisTemplate redisTemplate;
@Value("${shiro.globalSessionTimeout}")
private Long globExpire;//设置30分钟
@SuppressWarnings("rawtypes")
public RedisCache(String name, RedisTemplate client) {
this.cacheKey = REDIS_SHIRO_CACHE + name + ":";
this.redisTemplate = client;
}
@Override
public V get(K key) throws CacheException {
V value = redisTemplate.boundValueOps(getCacheKey(key)).get();
if(value!=null){
redisTemplate.boundValueOps(getCacheKey(key)).expire(globExpire, TimeUnit.MILLISECONDS);//毫秒
}
return value;
}
@Override
public V put(K key, V value) throws CacheException {
V old = get(key);
redisTemplate.boundValueOps(getCacheKey(key)).set(value);
redisTemplate.boundValueOps(getCacheKey(key)).expire(globExpire, TimeUnit.MILLISECONDS);//毫秒
return old;
}
@Override
public V remove(K key) throws CacheException {
V old = get(key);
redisTemplate.delete(getCacheKey(key));
return old;
}
@Override
public void clear() throws CacheException {
redisTemplate.delete(keys());
}
@Override
public int size() {
return keys().size();
}
@Override
public Set keys() {
return redisTemplate.keys(getCacheKey("*"));
}
/**
* 获取所有的value
*/
@Override
public Collection values() {
Set keys = this.keys();
List list = new ArrayList<>();
for (K key : keys) {
list.add(get(key));
}
return list;
}
private K getCacheKey(Object k) {
return (K) (this.cacheKey + k);
}
}
定义的常量
public class RedisConstants {
/**shiro相关*/
public static final String REDIS_SHIRO_CACHE = "dream-shiro-cache:";//shiro cache
public static final String REDIS_SHIRO_SESSION = "dream-shiro-session:";//shiro session
}
5、控制器登录方法,注意存放在
public ServiceResult login(@RequestParam("userName")String userName,@RequestParam("password")String password) {
userName = StringUtils.trim(userName);
password = StringUtils.trim(password);
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
logger.info("登录成功:sessionId="+subject.getSession().getId());
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
// 捕获密码错误异常
throw new ServiceException("密码错误");
} catch (UnknownAccountException e) {
e.printStackTrace();
// 捕获未知用户名异常
throw new ServiceException("不存在该用户名");
} catch (ExcessiveAttemptsException e) {
e.printStackTrace();
// 捕获错误登录过多的异常
throw new ServiceException("错误登录过多");
} catch (DisabledAccountException e) {
e.printStackTrace();
throw new ServiceException("该用户为禁用状态");
}
//取shiro的标识属性,在自定义reaml中赋值的
//User user = (User)subject.getPrincipal();
UserVO userVO = (UserVO)subject.getPrincipal();
//return Mono.just(new ServiceResult(userVO));
return new ServiceResult(userVO);
}
6、因为我搭建的项目是前后端分离,所以没有触发shiro的刷新生命周期的方法。这时自己在合适地方加上代码刷新声明周期,否则session到时间就会过期
// 手动刷新session生命周期,更新最后访问时间
session.touch();