缓存说明(本项目没有使用shiro的缓存管理器和session管理器)
shiro_user_cache:permission:权限缓存,当前只有test用户
shiro_user_cache:role:角色缓存,当前只有test用户
shiro_user_kickout:保存被踢出的用户
shiro_user_online: 保存登录了的用户
sprting:spring-session管理的缓存
上面缓存的创建过程
shiro_user_cache:登录时UserRealm会触发Spring的查询缓存保存用户的角色权限,清除缓存也是利用Spring的注解,如下
<cache:annotation-driven cache-manager="redisCacheManager" />
package com.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
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.springframework.beans.factory.annotation.Autowired;
import com.entity.User;
import com.service.UserService;
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String)principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(userService.findRolesByUsername(username));
authorizationInfo.setStringPermissions(userService.findPermissionsByUsername(username));
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal();
User user = userService.findByUsername(username);
if(user == null) {
throw new UnknownAccountException();//没找到帐号
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user.getUsername(),
user.getPassword(),
getName() //realm name
);
return authenticationInfo;
}
/**
* 根据用户名,清除角色和权限缓存
* @param uername
*/
public void clearUserCache(String uername) {
userService.clearUserCache(uername);
}
/**
* 清除所有用户的角色和权限缓存
*/
public void clearUserCache() {
userService.clearUserCache();
}
}
package com.service.impl;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.dao.DictDao;
import com.dao.RoleDao;
import com.dao.UserDao;
import com.entity.RolePermission;
import com.entity.User;
import com.entity.UserRole;
import com.service.UserService;
import core.service.BaseServiceImpl;
@Service
public class UserServiceImpl extends BaseServiceImpl implements UserService {
@Autowired
UserDao userDao;
@Autowired
RoleDao roleDao;
@Autowired
DictDao daoDao;
@Override
public User findByUsername(String username) {
return userDao.findByUsername(username);
}
@Cacheable(value="shiro_user_cache:role", key="#username")
@Override
public Set findRolesByUsername(String username) {
Set roles = new HashSet();
User user = this.findByUsername(username);
if(user==null) {
return roles;
}
List userRoles = user.getUserRoleList();
for(UserRole userRole:userRoles) {
roles.add(userRole.getRole().getName());
}
return roles;
}
@Cacheable(value="shiro_user_cache:permission", key="#username")
@Override
public Set findPermissionsByUsername(String username) {
Set permissions = new HashSet();
User user = this.findByUsername(username);
if(user==null) {
return permissions;
}
List userRoles = user.getUserRoleList();
for(UserRole userRole:userRoles) {
List rolePermissions= userRole.getRole().getRolePermissionList();
for(RolePermission rolePermission:rolePermissions) {
permissions.add(rolePermission.getPermission().getName());
}
}
return permissions;
}
@CacheEvict(value={"shiro_user_cache:role","shiro_user_cache:permission"}, key="#username")
public void clearUserCache(String username) {
}
@CacheEvict(value={"shiro_user_cache:role","shiro_user_cache:permission"}, allEntries=true)
public void clearUserCache() {
}
}
shiro_user_kickout和shiro_user_online,跟上面一样通过下面这个缓存管理器创建,通过他们实现单点登录或限定其他登录数.
"redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
factory-method="create" c:connection-factory-ref="jedisConnectionFactory" />
package com.shiro;
import java.io.Serializable;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import com.contant.SystemContant;
public class KickoutFilter extends AccessControlFilter {
/**
* 踢出前一个登陆或后一个登陆的同一用户
*/
private boolean kickoutBefore = true;
/**
* 同一个用户的最大同时登陆数
*/
private int maxUserCount = 1;
/**
* 保存同一用户登录数<用户名,sessionId队列>
*/
private Cache onliceCache;
/**
* 被踢出的登录<用户名,sessionId队列>
*/
private Cache kickoutCache;
@Autowired
@Qualifier("redisCacheManager")
public void setCacheManager(CacheManager cacheManager) {
this.onliceCache = cacheManager.getCache("shiro_user_online");
this.kickoutCache = cacheManager.getCache("shiro_user_kickout");
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception {
return false;
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = getSubject(request, response);
Session session = subject.getSession();
String username = (String) subject.getPrincipal();
Serializable sessionId = session.getId();
//如果没有登录,直接进行之后的流程
if (!subject.isAuthenticated() && !subject.isRemembered()) {
return true;
}
//先判断当前用户是否被踢出
Deque kickoutDeque = getKickoutDeque(username);
for (Serializable id : kickoutDeque) {
if (sessionId.equals(id)) {
subject.logout();
//踢出后在kickoutDeque中删除当前sessionId
System.out.println("踢出sessionId:" + id);
kickoutDeque.remove(id);
kickoutCache.put(username, kickoutDeque);
//跳转到登录页
Map params = new HashMap();
params.put(SystemContant.KICKOUT_MSG, "kick out login");
WebUtils.issueRedirect(request, response, "/login", params);
return false;
}
}
//如果队列里没有此sessionId,放入队列
Deque onlineDeque = getOnlineDeque(username);
if (!onlineDeque.contains(sessionId)) {
onlineDeque.push(sessionId);
}
//判断当前用户在线数目是否超出maxUserCount,然后把超出的用户从onlineDeque移到kickoutDeque
while (onlineDeque.size() > maxUserCount) {
Serializable kickoutSessionId = null;
if (kickoutBefore) {
kickoutSessionId = onlineDeque.removeLast();
kickoutDeque.push(kickoutSessionId);
} else {
kickoutSessionId = onlineDeque.removeFirst();
kickoutDeque.push(kickoutSessionId);
}
}
onliceCache.put(username, onlineDeque);
kickoutCache.put(username, kickoutDeque);
return true;
}
/**
* 获取在线用户
*
* @param username
* @return
*/
@SuppressWarnings("unchecked")
private Deque getOnlineDeque(String username) {
Deque onlineDeque;
if (onliceCache.get(username) == null) {
onlineDeque = new LinkedList();
} else {
onlineDeque = (Deque) onliceCache.get(username).get();
}
return onlineDeque;
}
/**
* 获取被踢出的用户
*
* @param username
* @return
*/
@SuppressWarnings("unchecked")
private Deque getKickoutDeque(String username) {
Deque kickoutDeque;
if (kickoutCache.get(username) == null) {
kickoutDeque = new LinkedList();
} else {
kickoutDeque = (Deque) kickoutCache.get(username).get();
}
return kickoutDeque;
}
}
配置文件
applicationContext-redis.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg>
<bean class="org.springframework.data.redis.connection.RedisStandaloneConfiguration"
c:host-name="${redis.host}" c:port="${redis.port}" />
constructor-arg>
bean>
<bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
factory-method="create" c:connection-factory-ref="jedisConnectionFactory" />
beans>
applicationContext-session.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
<property name="maxInactiveIntervalInSeconds" value="1800" />
bean>
beans>
applicationContext-shiro.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm">
<bean class="com.shiro.UserRealm"/>
property>
bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login" />
<property name="unauthorizedUrl" value="/login" />
<property name="filters">
<util:map>
<entry key="kickout">
<bean class="com.shiro.KickoutFilter" />
entry>
util:map>
property>
<property name="filterChainDefinitions">
<value>
/login/** = anon
/common/taglibs.jspf = anon
/static/** = anon
/** = kickout,authc
value>
property>
bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
beans>
applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context" xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:jpa="http://www.springframework.org/schema/data/jpa"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"
default-autowire="byName">
<context:annotation-config />
<context:component-scan base-package="com.service,core" />
<bean id="propertyConfig"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>classpath:application.propertiesvalue>
property>
<property name="fileEncoding">
<value>UTF-8value>
property>
bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<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="1" />
<property name="maxActive" value="100" />
<property name="minIdle" value="10" />
<property name="maxWait" value="60000" />
<property name="timeBetweenEvictionRunsMillis" value="600000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="validationQuery" value="SELECT 1 FROM DUAL " />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<property name="filters" value="stat" />
bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource">property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">bean>
property>
<property name="packagesToScan" value="com.entity">property>
<property name="jpaProperties">
<props>
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategyprop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialectprop>
<prop key="hibernate.show_sql">trueprop>
<prop key="hibernate.use_sql_comments">trueprop>
<prop key="hibernate.hbm2ddl.auto">updateprop>
props>
property>
bean>
<aop:aspectj-autoproxy expose-proxy="true"
proxy-target-class="false" />
<tx:annotation-driven transaction-manager="transactionManager"
proxy-target-class="false" />
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory">property>
bean>
<jpa:repositories base-package="com.dao" repository-impl-postfix="Impl"
entity-manager-factory-ref="entityManagerFactory" transaction-manager-ref="transactionManager"/>
<cache:annotation-driven cache-manager="redisCacheManager" />
<bean id="redisUtil" class="core.util.RedisUtil" />
beans>
springServletContext.xml
<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"
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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven />
<mvc:view-controller path="/" view-name="redirect:/login"/>
<context:component-scan base-package="com.controller">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
context:component-scan>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
bean>
<bean id="JstlView"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="order" value="1" />
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
bean>
beans>
源码地址