spring boot 2.x 项目shiro使用redis管理session和缓存

    之前搞好了忘记记录下来,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配置文件

spring boot 2.x 项目shiro使用redis管理session和缓存_第1张图片

我自己定义的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 redisTemplate;

    @Override
    public  Cache getCache(String name) throws CacheException {
        return new RedisCache(name, 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();

 

你可能感兴趣的:(springboot)