Shiro+Spring+redis的实现


Shiro+Spring+redis的实现

Shiro

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。
三个核心组件:Subject, SecurityManager 和 Realms.
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
  Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
  SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
  Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
  从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
  Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

Shiro的整体架构

  1. 上面标记为1的是shiro的主体部分subject,可以理解为当前的操作用户
  2. Security Manager为Shiro的核心,shiro是通过security Manager来提供安全服务的,security Manager管理着Session Manager、Cache Manager等其他组件的实例:Authenticator(认证器,管理我们的登录登出) Authorizer(授权器,负责赋予主体subject有哪些权限) Session Manager(shiro自己实现的一套session管理机制,可以不借助任何web容器的情况下使用session) Session Dao(提供了session的增删改查操作) cache Manager(缓存管理器,用于缓存角色数据和权限数据) Pluggable Realms(shiro与数据库/数据源之间的桥梁,shiro获取认证信息、权限数据、角色数据都是通过Realms来获取)
  3. 上图标记为2的cryptography是用来做加密的,使用它可以非常方便快捷的进行数据加密。
  4. 上面箭头的流程可以这样理解:主体提交请求到Security Manager,然后由Security Manager调用Authenticator去做认证,而Authenticator去获取认证数据的时候是通过Realms从数据源中来获取的,然后把从数据源中拿到的认证信息与主体提交过来的认证信息做比对。授权器Authorizer也是一样。

项目搭建

项目配置

结构:

数据库配置database.properties:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf-8&useSSL=false&autoReconnect=true&rewriteBatchedStatements=true&serverTimezone=UTC
jdbc.username=root
jdbc.password=root

MyBatis-config:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--配置全局设置-->
    <settings>
        <!--启用日志,并指定日志实现方式-->
        <setting name="logImpl" value="SLF4J"/>

        <!--启用主键生成策略-->
        <setting name="useGeneratedKeys" value="true"/>

        <!--配置启用下划线转驼峰的映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>

        <!--启用二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>
</configuration>

redis.xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:task="http://www.springframework.org/schema/task"
       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/tx
	http://www.springframework.org/schema/tx/spring-tx.xsd
	http://www.springframework.org/schema/task
	http://www.springframework.org/schema/task/spring-task.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop.xsd "
       default-autowire="byName" default-lazy-init="false">
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxIdle" value="300" />
        <property name="testOnBorrow" value="true" />
    </bean>
    <bean id="jedisConnectionFactory"  class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <property name="poolConfig" ref="poolConfig" />
        <!-- <property name="hostName" value="120.25.226.150" />
        <property name="port" value="4651" />
        <property name="timeout" value="2000" />
        <property name="password" value="Ru4uTUCYk!#ENxNEK9bzJ%CYZUBJ" /> -->
        <property name="hostName" value="127.0.0.1" />
        <property name="port" value="6379" />
        <property name="timeout" value="2000" />
        <property name="password" value="" />
    </bean>

    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
          p:connection-factory-ref="jedisConnectionFactory">
    </bean>

	/*  */
    <bean id="jedisManager" class="com.zwq.ssm.util.JedisManager">
        <property name="redisTemplate" ref="redisTemplate"></property>
    </bean>
</beans>

spring-mvc.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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       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/context
       http://www.springframework.org/schema/context/spring-context.xsd
	   http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 基于@Aspect切面的驱动器 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />
    <!--配置视图解析器,这样控制器里就直接返回文件名就好了-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀-->
        <property name="prefix" value="/WEB-INF/page/"/>
        <!--后缀-->
        <property name="suffix" value=".html"/>
    </bean>

    <!--配置静态资源过滤,不然静态资源比如css是访问不到的-->
    <mvc:default-servlet-handler/>

    <!-- 处理静态资源文件的访问 -->
    <mvc:resources mapping="/api/**" location="/WEB-INF/api/" />
    <mvc:resources mapping="/css/**" location="/WEB-INF/css/" />
    <mvc:resources mapping="/images/**" location="/WEB-INF/images/" />
    <mvc:resources mapping="/js/**" location="/WEB-INF/js/" />
    <mvc:resources mapping="/lib/**" location="/WEB-INF/lib/" />


    <!--配置扫描的包-->
    <context:component-scan base-package="com.zwq.ssm.controller" use-default-filters="false">
        <!--只扫描controller,实际开发中最好用这种方式来写,这边MVC就只扫描controller,就不会IOC那边冲突,否则事务会被覆盖,IOC那边就要排除这个controller-->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--启用MVC的注解-->
    <mvc:annotation-driven/>
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
        <property name="proxyTargetClass" value="true" />
    </bean>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>
</beans>

spring-mybatis.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:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--属性文件的读取,这里读取刚才我们的数据库连接相关配置-->
    <context:property-placeholder location="classpath:database.properties" file-encoding="UTF-8"/>

    <!--配置自动扫描,如果不配置这个那么就无法使用@Autowired加载bean-->
    <context:component-scan base-package="com.zwq.ssm" use-default-filters="true">
        <!--这里要排除掉Controller的注解,Controller专门交给MVC去扫描,这样会就不会冲突-->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!--配置JDBC基础属性,即数据库连接相关配置-->
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>

        <!--配置连接池的设置,这个是要根据真实项目情况来配置的,随着项目的发展会不断修改-->
        <property name="initialSize" value="10"/>
        <property name="maxActive" value="100"/>
    </bean>

    <!--
    重点来了,这里配置是MyBatis的SqlSessionFactory,就是这一块配置将Spring和Mybatis整合到了一起
    如果不配置这里,你的mapper接口只能通过SqlSession来获取,十分麻烦。这里配置好后就可以通过Spring IoC来获取mapper接口了
    -->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
        <!--指定数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--加载mybatis全局设置,classpath即我们的资源路径resources-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
		<!--这里是mybatis分页-->
        <property name="plugins">
            <array>
                <bean class="com.github.pagehelper.PageHelper">
                    <property name="properties">
                        <value>
                            dialect=mysql
                        </value>
                    </property>
                </bean>
            </array>
        </property>

    </bean>

    <!--指定Mybatis的mapper接口扫描包-->
    <!--注意!!!如果用的是tk.mybatis自动生成的mapper接口,一定要将org.mybatis.改成tk.mybatis-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定刚才我们配置好的sqlSessionFactory-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!--指定mapper接口扫描包-->
        <property name="basePackage" value="com.zwq.ssm.mapper"/>
    </bean>

    <!--配置事务管理器,如果不配置这个,不启动事务扫描的话,那么发生了异常也不会触发回滚-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--还得指定数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--启动事务的扫描-->
    <tx:annotation-driven/>
</beans>

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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
			http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
			http://www.springframework.org/schema/mvc
    		http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-4.0.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

    <!-- Shiro的web过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"></property>
        <!--登录页面 如果没有登录 访问项目的方法或页面 直接跳转到这个页面 -->
        <property name="loginUrl" value="/index"></property>
        <!--登录后 在访问没有经过授权的方法或页面时 直接跳转到这个页面 -->
        <property name="unauthorizedUrl" value="/index"></property>

        <property name="filterChainDefinitions">
            <value>
                /api/** = anon
                /css/** = anon
                /images/** = anon
                /js/** = anon
                /lib/** = anon
                /=anon
            </value>
        </property>
    </bean>

    <!-- 自定义realm -->
    <bean id="realm" class="com.zwq.ssm.shiro.CustomShiroRealm">
    </bean>

	<!--主要是用来实现项目中出现多realm的情况-->
    <bean id="authenticator" class="com.zwq.ssm.shiro.CustomModularRealmAuthenticator">
        <property name="authenticationStrategy">
            <bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy" />
        </property>
    </bean>

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="authenticator" ref="authenticator"/>
        <property name="realm" ref="realm"/>
        <property name="sessionManager" ref="sessionManager"/>
        <property name="cacheManager" ref="cacheManager" />
    </bean>

    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <property name="globalSessionTimeout" value="1800000" />
        <property name="deleteInvalidSessions" value="true" />
        <property name="sessionValidationSchedulerEnabled" value="true" />
        <property name="sessionDAO" ref="sessionDao" />
        <!-- <property name="sessionIdCookie" ref="sessionIdCookie" /> -->
    </bean>

    <bean id="sessionDao" class="com.zwq.ssm.shiro.RedisSessionDao">
        <property name="jedisManager" ref="jedisManager" />
    </bean>

    <!-- 配置缓存管理器 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />

    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

</beans>

web.xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">

    <!--1.配置Spring IOC容器的创建,如果不配置这个,Mybatis就在web应用里无法使用-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <!--spring和mybatis整合配置文件路径-->
        <param-value>
            classpath:spring-mybatis.xml,
            classpath:spring-shiro-web.xml,
            classpath:redis.xml
        </param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!--2.配置SpringMVC的前端控制器-->
    <servlet>
        <servlet-name>SpringMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!--SpringMVC整合配置文件路径-->
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- shiro filter 的配置要在别的配置之前,保证能够拦截到所有的请求 -->
         <filter>
             <filter-name>shiroFilter</filter-name>
             <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
             <init-param>
                 <param-name>targetFilterLifecycle</param-name>
                <param-value>true</param-value>
             </init-param>
         </filter>
         <filter-mapping>
             <filter-name>shiroFilter</filter-name>
             <url-pattern>/*</url-pattern>
         </filter-mapping>

    <!--3.配置字符编码过滤器-->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
		<!--禁止强制,否则不能使用html后缀-->
        <!--<init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>-->
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- session有效时间 -->
        <session-config>
           <session-timeout>30</session-timeout>
        </session-config>

       <welcome-file-list>
         <welcome-file>index</welcome-file>
       </welcome-file-list>
</web-app>

shiro类:
CustomModularRealmAuthenticator:主要是用来实现项目中出现多realm的情况:

import org.apache.shiro.ShiroException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.util.CollectionUtils;

import java.util.Collection;

public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator {  
    /*主要是用来实现项目中出现多realm的情况*/
    @Override  
    protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
        System.out.println("CustomModularRealmAuthenticator");
        AuthenticationStrategy strategy = getAuthenticationStrategy();  
        AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);  
          
        for (Realm realm : realms) {  
            aggregate = strategy.beforeAttempt(realm, token, aggregate);  
            if (realm.supports(token)) {  
                AuthenticationInfo info = null;  
                Throwable t = null;  
                try {  
                    info = realm.getAuthenticationInfo(token);  
                } catch (Throwable throwable) {  
                    if( throwable instanceof ShiroException) {
                    	throw throwable;
                    }
                    t = throwable;  
                    throwable.printStackTrace();
                }   
                aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);  
                // dirty dirty hack  
                if (aggregate != null && !CollectionUtils.isEmpty(aggregate.getPrincipals())) {  
                    return aggregate;  
                }  
                // end dirty dirty hack  
            } else {  
  
            }  
        }  
        aggregate = strategy.afterAllAttempts(token, aggregate);  
        return aggregate;  
    }  
}  

自定义realm类:CustomShiroRealm:

import com.sun.jmx.snmp.internal.SnmpOutgoingRequest;
import com.zwq.ssm.comm.Cons;
import com.zwq.ssm.dt.UserDT;
import com.zwq.ssm.pojo.Permission;
import com.zwq.ssm.pojo.User;
import com.zwq.ssm.service.PermissionService;
import com.zwq.ssm.service.UserService;
import org.apache.shiro.SecurityUtils;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

/**
 * description: 自定义realm类
 * @author zwq
 * @date 2021/12/15 10:23
 */
public class CustomShiroRealm extends AuthorizingRealm {

	Logger logger = LoggerFactory.getLogger(this.getClass());

	@Autowired
	private UserService userService;

	@Autowired
	private PermissionService permissionService;

	@Override
	protected void onInit() {
		super.onInit();
	}

	@Override
	public boolean isPermitted(PrincipalCollection principals, String permission) {
		System.out.println("isPermitted");
		UserDT user = (UserDT) SecurityUtils.getSubject().getSession().getAttribute(Cons.USER_SESSION_ID);
		if(user.getRoleid() == Cons.USER_ADMIN) {
			// 管理员
			return true;
		}
		return super.isPermitted(principals, permission.replaceAll(":", "#"));
	}

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
		System.out.println("授权");
		UserDT userDT = (UserDT) SecurityUtils.getSubject().getSession().getAttribute(Cons.USER_SESSION_ID);
		if (userDT != null) {
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			if(userDT.getRoleid() != Cons.USER_ADMIN) {
				System.out.println("权限");
				List<Permission> pers = userDT.getPers();
				if(null != pers && !pers.isEmpty()) {
					for(Permission per : pers) {
						if(!per.getCode().isEmpty()) {
							info.addStringPermission(per.getCode().replaceAll(":", "#"));
						}
					}
				}
			} else {
				info.addStringPermission(Cons.AUTH_ALL);
			}
			info.addStringPermission(Cons.AUTH_NORMAL);
			System.out.println("return info");
			return info;
		}
		return null;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
		System.out.println("登录");
		// UsernamePasswordToken对象用来存放提交的登录信息
		UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
		System.out.println("token:"+token.toString());
		logger.info("token:"+token.toString());
		User user = null;
		// 查出是否有此用户
		user = userService.selectByUsername(token.getUsername());
		if (null != user) {
			/*user.setHost(User.HOST_BG);*/
		} else {
			System.out.println("没有此用户");
			throw new UnknownAccountException();
		}
		if (user != null) {
			// 验证用户是否有效
			UserDT userDT = new UserDT();
			BeanUtils.copyProperties(user, userDT);
			userDT.setUserId(user.getId());
			userDT.setRoleid(user.getRoleid());
			logger.info("userDT:"+userDT.toString());
			System.out.println("userDT:"+userDT.toString());
			if (userDT.getAvailable() != 0) {
				throw new LockedAccountException("账户无效!");
			} else if (userDT.getPassword().equals(String.valueOf(token.getPassword()))) {
				if(userDT.getRoleid() != Cons.USER_ADMIN) {
					System.out.println("bushi");
					userDT.setPers(permissionService.selectListsByRoleid(userDT.getRoleid()));
				}else {
					userDT.setPers(permissionService.selectListsAll());
				}
				// 保存登陆用户Session,并设置session有时间(ms)
				setSession(Cons.USER_SESSION_ID, userDT, 1800000);
				// 2018-10-30 用户登录时加载当前系统所有权限
				setSession(Cons.SYSTEM_PERMISSION, permissionService.selectListsAll(), 1800000);
				System.out.println("other");
			}
			// 若存在,将此用户存放到登录认证info中
			System.out.println("fanhui");
			return new SimpleAuthenticationInfo(userDT.getUsername(), userDT.getPassword(), getName());
		}

		return null;
	}

	private void setSession(Object key, Object value, long time) {
		Subject currentUser = SecurityUtils.getSubject();
		/*System.out.println("currentUser:"+currentUser.toString());*/
		if (null != currentUser) {
			Session session = currentUser.getSession();
			// 设置session有效期
			session.setTimeout(time);
			if (null != session) {
				session.setAttribute(key, value);
			}
		}
		System.out.println("over");
	}
}

将session存入redis的类:RedisSessionDao:

import com.zwq.ssm.comm.Cons;
import com.zwq.ssm.dt.UserDT;
import com.zwq.ssm.util.JedisManager;
import com.zwq.ssm.util.SerializerUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
 * description: 将session存入redis的类
 * @author zwq
 * @date 2021/12/15 10:23
 */
public class RedisSessionDao extends AbstractSessionDAO {
	
	private static final String SESSION_PREFIX = Cons.SESSION_PREFIX;
	private static final long AGE_OTH = 1800l;
	private JedisManager jedisManager;

	public JedisManager getJedisManager() {
		return jedisManager;
	}

	public void setJedisManager(JedisManager jedisManager) {
		this.jedisManager = jedisManager;
	}

	@Override
	public void update(Session session) throws UnknownSessionException {
		saveSession(session);
	}
	
	private void saveSession(Session session) {
		System.out.println(session);
		if(null == session || null == session.getId()) {
			return;
		}
		Object target = session.getAttribute(Cons.USER_SESSION_ID);
		if(null == target) {
			jedisManager.putObject(SESSION_PREFIX + session.getId(), session, session.getTimeout() / 1000);
			return;
		}
		if(target instanceof UserDT) {
			UserDT UserDT = ((UserDT) target);
			jedisManager.putObject(SESSION_PREFIX + session.getId(), session, AGE_OTH);

		} else {
			jedisManager.putObject(SESSION_PREFIX + session.getId(), session, AGE_OTH);
		}
	}

	@Override
	public void delete(Session session) {
		jedisManager.del(SESSION_PREFIX + session.getId());
	}

	@Override
	public Collection<Session> getActiveSessions() {
		Set<byte[]> keys = jedisManager.keys(SESSION_PREFIX + "*");
		Set<Session> sessions = new HashSet<Session>();
		if(null != keys && !keys.isEmpty()) {
			for(byte[] key : keys) {
				Session session = SerializerUtils.objectDeserialize(key);
				if(null != session) {
					sessions.add(session);
				}
			}
		}
		return sessions;
	}

	@Override
	protected Serializable doCreate(Session session) {
		Serializable sessionId = generateSessionId(session);    
        assignSessionId(session, sessionId);  
        saveSession(session);  
        return sessionId;
	}

	@Override
	protected Session doReadSession(Serializable sessionId) {
		if(sessionId == null){  
		    return null;  
		}
		return jedisManager.getObject(SESSION_PREFIX + sessionId, Session.class);
	}

}

redis工具类:

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.io.Serializable;
import java.util.Set;

/**
 * description: redis工具
 * @author zwq
 * @date 2021/12/15 10:24
 */
@SuppressWarnings("unchecked")
public class JedisManager {
	
	private RedisTemplate<Serializable, Serializable> redisTemplate;

	
	public boolean setnx(final String key) {
		return redisTemplate.execute(new RedisCallback<Boolean>() {

			@Override
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
				long now = System.currentTimeMillis();
				RedisSerializer<Long> ds = (RedisSerializer<Long>) redisTemplate.getDefaultSerializer();
				return connection.setNX(redisTemplate.getStringSerializer().serialize(key), ds.serialize(now));
			}
			
		});
	}

	public long getset(final String key) {
		return redisTemplate.execute(new RedisCallback<Long>() {

			@Override
			public Long doInRedis(RedisConnection connection) throws DataAccessException {
				long now = System.currentTimeMillis();
				RedisSerializer<Long> ds = (RedisSerializer<Long>) redisTemplate.getDefaultSerializer();
				byte[] ret = connection.getSet(redisTemplate.getStringSerializer().serialize(key), ds.serialize(now));
				return ds.deserialize(ret);
			}
			
		});
	}
	public int dbSize() {
		return redisTemplate.execute(new RedisCallback<Integer>() {

			@Override
			public Integer doInRedis(RedisConnection connection) throws DataAccessException {
				return connection.dbSize().intValue();
			}
			
		});
	}

	public void flushAll() {
		redisTemplate.execute(new RedisCallback<Object>() {

			@Override
			public Object doInRedis(RedisConnection connection) throws DataAccessException {
				connection.flushAll();
				return null;
			}
			
		});
	}
	
	public void flushDb() {
		redisTemplate.execute(new RedisCallback<Object>() {

			@Override
			public Object doInRedis(RedisConnection connection) throws DataAccessException {
				connection.flushDb();
				return null;
			}
			
		});
	}
	
	public Set<byte[]> keys(final String partten) {
		 return redisTemplate.execute(new RedisCallback<Set<byte[]>>() {

			@Override
			public Set<byte[]> doInRedis(RedisConnection connection) throws DataAccessException {
				//connection.keys(pattern)//"shirosid_"
				byte[] p = redisTemplate.getStringSerializer().serialize(partten);
				Set<byte[]> keys = connection.keys(p);
				return keys;
			}
			
		});
	}

	public void del(final String ... keys) {
		redisTemplate.execute(new RedisCallback<Object>() {
			@Override
			public Object doInRedis(RedisConnection connection) throws DataAccessException {
				if(null != keys && keys.length > 0) {
					byte[][] ks = new byte[keys.length][1];
					int i = 0;
					for(String key : keys ) {
						byte[] k = redisTemplate.getStringSerializer().serialize(key);
						ks[i ++] = k;
					}
					connection.del(ks);
				}
				return null;
			}
		});
	}
	
	public <T> T getObject(final String key, final Class<T> t) {
		try {
			return redisTemplate.execute(new RedisCallback<T>() {
				@Override
				public T doInRedis(RedisConnection connection) throws DataAccessException {
					byte[] k = redisTemplate.getStringSerializer().serialize(key);
					return (T) redisTemplate.getDefaultSerializer().deserialize(connection.get(k));
//					Jackson2JsonRedisSerializer<T> ser = new Jackson2JsonRedisSerializer<T>(t);
//					return ser.deserialize(connection.get(k));
				}
			});
		} catch(Exception ex) {
			//ex.printStackTrace();
			return null;
		}
	}
	
	public <T> T getObject(final String key) {
		try {
			return redisTemplate.execute(new RedisCallback<T>() {
				@Override
				public T doInRedis(RedisConnection connection) throws DataAccessException {
					byte[] k = redisTemplate.getStringSerializer().serialize(key);
					return (T) redisTemplate.getDefaultSerializer().deserialize(connection.get(k));
//					Jackson2JsonRedisSerializer<T> ser = new Jackson2JsonRedisSerializer<T>(t);
//					return ser.deserialize(connection.get(k));
				}
			});
		} catch(Exception ex) {
			//ex.printStackTrace();
			return null;
		}
	}

	public <T> T putObject(final String key, final T t) {
		return redisTemplate.execute(new RedisCallback<T>() {
			@Override
			public T doInRedis(RedisConnection connection) throws DataAccessException {
				byte[] k = redisTemplate.getStringSerializer().serialize(key);
				RedisSerializer<T> ds = (RedisSerializer<T>) redisTemplate.getDefaultSerializer();
				connection.set(k, ds.serialize(t));
				//Jackson2JsonRedisSerializer<T> ser = new Jackson2JsonRedisSerializer<T>((Class<T>) t.getClass());
				//connection.set(k, ser.serialize(t));
				return null;
			}
		});
	}
	
	public <T> void putObject(final String key, final T t, final long sec) {
		if(sec <= 0) {
		//	del(key);
		} else {
			redisTemplate.execute(new RedisCallback<T>() {
				@Override
				public T doInRedis(RedisConnection connection) throws DataAccessException {
					byte[] k = redisTemplate.getStringSerializer().serialize(key);
					RedisSerializer<T> ds = (RedisSerializer<T>) redisTemplate.getDefaultSerializer();
					connection.setEx(k, sec, ds.serialize(t));
					//Jackson2JsonRedisSerializer<T> ser = new Jackson2JsonRedisSerializer<T>((Class<T>) t.getClass());
					//connection.setEx(k, sec, ser.serialize(t));
					//connection.del(keys)
					return null;
				}
			});
		}
	}
	
	public String get(final String key) {
		try {
			return redisTemplate.execute(new RedisCallback<String>() {

				@Override
				public String doInRedis(RedisConnection connection) throws DataAccessException {
					byte[] k = redisTemplate.getStringSerializer().serialize(key);
					return redisTemplate.getStringSerializer().deserialize(connection.get(k));
				}
			});
		} catch(Exception ex) {
			//ex.printStackTrace();
			return null;
		}
	}

	public void put(final String key, final String value, final long sec) {
		redisTemplate.execute(new RedisCallback<String>() {

			@Override
			public String doInRedis(RedisConnection connection) throws DataAccessException {
				byte[] k = redisTemplate.getStringSerializer().serialize( key);
				byte[] val = redisTemplate.getStringSerializer().serialize(value);
				connection.setEx(k, sec, val);
				return null;
			}
		});
	}
	public void put(final String key, final String value) {
		redisTemplate.execute(new RedisCallback<String>() {

			@Override
			public String doInRedis(RedisConnection connection) throws DataAccessException {
				byte[] k = redisTemplate.getStringSerializer().serialize( key);
				byte[] val = redisTemplate.getStringSerializer().serialize(value);
				connection.set(k, val);
				return null;
			}
		});
	}

	public RedisTemplate<Serializable, Serializable> getRedisTemplate() {
		return redisTemplate;
	}

	public void setRedisTemplate(RedisTemplate<Serializable, Serializable> redisTemplate) {
		this.redisTemplate = redisTemplate;
	}

}

序列化工具:

import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

public abstract class SerializerUtils {
	private static StringRedisSerializer string = new StringRedisSerializer();
	private static JdkSerializationRedisSerializer jdk = new JdkSerializationRedisSerializer();
	
	public static String stringDeserialize(byte[] bytes) {
		return string.deserialize(bytes);
	}
	public static byte[] stringSerialize(String str) {
		return string.serialize(str);
	}
	
	@SuppressWarnings("unchecked")
	public static <T> T objectDeserialize(byte[] bytes) {
		return (T) jdk.deserialize(bytes);
	}

	public static <T> byte[] objectSerialize(T t) {
		return jdk.serialize(t);
	}
	
	
}

  目录