大家好,又见面了,我是你们的朋友全栈君。
<!-- 启用缓存注解功能,在这里起到关键作用 -->
<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<User, Integer> 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<String> findRolesByUsername(String username) {
Set<String> roles = new HashSet<String>();
User user = this.findByUsername(username);
if(user==null) {
return roles;
}
List<UserRole> 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<String> findPermissionsByUsername(String username) {
Set<String> permissions = new HashSet<String>();
User user = this.findByUsername(username);
if(user==null) {
return permissions;
}
List<UserRole> userRoles = user.getUserRoleList();
for(UserRole userRole:userRoles) {
List<RolePermission> 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,跟上面一样通过下面这个缓存管理器创建,通过他们实现单点登录或限定其他登录数.
<bean id="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<Serializable> 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<String, String> params = new HashMap<String, String>();
params.put(SystemContant.KICKOUT_MSG, "kick out login");
WebUtils.issueRedirect(request, response, "/login", params);
return false;
}
}
//如果队列里没有此sessionId,放入队列
Deque<Serializable> 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<Serializable> getOnlineDeque(String username) {
Deque<Serializable> onlineDeque;
if (onliceCache.get(username) == null) {
onlineDeque = new LinkedList<Serializable>();
} else {
onlineDeque = (Deque<Serializable>) onliceCache.get(username).get();
}
return onlineDeque;
}
/** * 获取被踢出的用户 * * @param username * @return */
@SuppressWarnings("unchecked")
private Deque<Serializable> getKickoutDeque(String username) {
Deque<Serializable> kickoutDeque;
if (kickoutCache.get(username) == null) {
kickoutDeque = new LinkedList<Serializable>();
} else {
kickoutDeque = (Deque<Serializable>) kickoutCache.get(username).get();
}
return kickoutDeque;
}
}
配置文件
applicationContext-redis.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: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
<?xml version="1.0" encoding="UTF-8"?>
<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
<?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: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>
<!-- Shiro Filter -->
<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>
<!-- Shiro的单点登录-->
<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
<?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: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.properties</value>
</property>
<property name="fileEncoding">
<value>UTF-8</value>
</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" />
<!-- 配置间隔10分钟,检测空闲了5分钟的连接是否需要关闭,单位是毫秒 -->
<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.ImprovedNamingStrategy</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.use_sql_comments">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 使用JDK代理方式配置AOP,暴露代理到Threadload,解决内部调用存在事务再另起事务失效问题 -->
<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>
<!-- Spring Data Jpa配置 -->
<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
<?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" 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>
<!-- Enable Shiro Annotations for Spring-configured beans. Only run after -->
<!-- the lifecycleBeanProcessor has run: -->
<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>