一、概述
Apache Shiro是Java的一个安全框架,旨在简化身份验证和授权。Shiro在JavaSE和JavaEE项目中都可以使用。它主要用来处理身份认证,授权,企业会话管理和加密等。Shiro的具体功能点如下:
(1)身份认证/登录,验证用户是不是拥有相应的身份;
(2)授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
(3)会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
(4)加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
(5)Web支持,可以非常容易的集成到Web环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
(6)shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
(7)提供测试支持;
(8)允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
(9)记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
文字描述可能并不能让猿友们完全理解具体功能的意思。下面我们以登录验证为例,向猿友们介绍Shiro的使用。至于其他功能点,猿友们用到的时候再去深究其用法也不迟。
二、Shiro实例详细说明
1)依赖包
<!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> <version>2.0.0.M2</version> </dependency>2)web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:shiro.xml</param-value> </context-param> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <async-supported>true</async-supported> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <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> <filter> <filter-name>csrfFilter</filter-name> <filter-class>top.ibase4j.core.filter.CsrfFilter</filter-class> </filter> <filter-mapping> <filter-name>csrfFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <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> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <listener> <listener-class>org.ibase4j.core.ServerListener</listener-class> </listener> <session-config> <session-timeout>30</session-timeout> </session-config> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>3).shiro.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 --> <bean id="sessionDAO" class="top.ibase4j.core.support.cache.shiro.RedisSessionDAO" /> <bean id="realm" class="org.ibase4j.core.shiro.Realm" /> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="realm" /> <property name="cacheManager"> <bean class="top.ibase4j.core.support.cache.shiro.RedisCacheManager" /> </property> <property name="sessionManager" ref="sessionManager" /> <property name="rememberMeManager" ref="rememberMeManager" /> </bean> <bean id="sessionListener" class="top.ibase4j.core.listener.SessionListener" /> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="sessionDAO" ref="sessionDAO" /> <property name="sessionListeners"> <list> <ref bean="sessionListener" /> </list> </property> <property name="sessionIdCookie"> <bean class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="IBASE4JSESSIONID" /> <property name="httpOnly" value="true" /> <property name="maxAge" value="-1" /> <property name="path" value="/" /> <property name="secure" value="${session.cookie.secure}" /> </bean> </property> </bean> <!-- rememberMe管理器 --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cookie"> <!-- rememberMe缓存cookie --> <bean class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="rememberMe" /> <!-- 不会暴露给客户端 --> <property name="httpOnly" value="true" /> <!-- 记住我cookie生效时间 --> <property name="maxAge" value="${rememberMe.cookie.maxAge}" /> </bean> </property> </bean> <!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- Shiro的核心安全接口,这个属性是必须的 --> <property name="securityManager" ref="securityManager" /> <!-- 要求登录时的链接 --> <property name="loginUrl" value="/unauthorized" /> <!-- 用户访问未对其授权的资源时,所显示的连接 --> <property name="unauthorizedUrl" value="/forbidden" /> <!-- Shiro连接约束配置,即过滤链的定义 --> <!-- anon:它对应的过滤器里面是空的,什么都没做 --> <!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter --> <property name="filterChainDefinitions"> <value> /=anon /app/**=anon /index.jsp=anon /regin=anon /login=anon /*.ico=anon /upload/*=anon /unauthorized=anon /forbidden=anon /sns*=anon /*/api-docs=anon /callback*=anon /swagger*=anon /configuration/*=anon /*/configuration/*=anon /webjars/**=anon /**=authc,user </value> </property> </bean> <!-- Shiro生命周期处理器 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> <!-- 开启Shiro Spring AOP权限注解@RequiresPermissions的支持 --> <bean depends-on="lifecycleBeanPostProcessor" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"> <property name="proxyTargetClass" value="true" /> </bean> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager" /> </bean> </beans>
RedisCacheManage.java
package top.ibase4j.core.support.cache.shiro; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; public class RedisCacheManager implements CacheManager { private final Logger logger = LogManager.getLogger(); private final ConcurrentMap<String, Cache> caches = new ConcurrentHashMap(); private String keyPrefix = "S:iBase4J:SHIRO-CACHE:"; public RedisCacheManager() { } public String getKeyPrefix() { return this.keyPrefix; } public void setKeyPrefix(String keyPrefix) { this.keyPrefix = keyPrefix; } public <K, V> Cache<K, V> getCache(String name) throws CacheException { this.logger.debug("获取名称为: " + name + " 的RedisCache实例"); Cache c = (Cache)this.caches.get(name); if (c == null) { RedisCache cache = new RedisCache(this.keyPrefix); this.caches.put(name, cache); } return c; } }
package top.ibase4j.core.support.cache.shiro; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.util.CollectionUtils; import top.ibase4j.core.util.CacheUtil; public class RedisCache<K, V> implements Cache<K, V> { private final Logger logger = LogManager.getLogger(); private String keyPrefix = "shiro_redis_session:"; public String getKeyPrefix() { return this.keyPrefix; } public void setKeyPrefix(String keyPrefix) { this.keyPrefix = keyPrefix; } public RedisCache(String prefix) { this.keyPrefix = prefix; } public V get(K key) throws CacheException { this.logger.debug("根据key从Redis中获取对象 key [" + key + "]"); Object value = CacheUtil.getCache().getFire(this.getKey(key)); return value; } public V put(K key, V value) throws CacheException { this.logger.debug("根据key从存储 key [" + key + "]"); CacheUtil.getCache().set(this.getKey(key), (Serializable)value); return value; } public V remove(K key) throws CacheException { this.logger.debug("从redis中删除 key [" + key + "]"); Object previous = this.get(key); CacheUtil.getCache().del(this.getKey(key)); return previous; } public void clear() throws CacheException { this.logger.debug("从redis中删除所有元素"); CacheUtil.getCache().delAll(this.keyPrefix + "*"); } public int size() { return CacheUtil.getCache().getAll(this.keyPrefix + "*").size(); } public Set<K> keys() { Set keys = CacheUtil.getCache().getAll(this.keyPrefix + "*"); if(CollectionUtils.isEmpty(keys)) { return Collections.emptySet(); } else { HashSet newKeys = new HashSet(); Iterator var3 = keys.iterator(); while(var3.hasNext()) { Object key = var3.next(); newKeys.add(key); } return newKeys; } } public Collection<V> values() { Set keys = CacheUtil.getCache().getAll(this.keyPrefix + "*"); if(!CollectionUtils.isEmpty(keys)) { ArrayList values = new ArrayList(keys.size()); Iterator var3 = keys.iterator(); while(var3.hasNext()) { Object key = var3.next(); Object value = this.get(key); if(value != null) { values.add(value); } } return Collections.unmodifiableList(values); } else { return Collections.emptyList(); } } private String getKey(K key) { return this.keyPrefix + key; } }
package top.ibase4j.core.util; import top.ibase4j.core.support.cache.CacheManager; import top.ibase4j.core.util.PropertiesUtil; public class CacheUtil { private static CacheManager cacheManager; private static CacheManager lockManager; public CacheUtil() { } public static void setCacheManager(CacheManager cacheManager) { CacheUtil.cacheManager = cacheManager; } public static void setLockManager(CacheManager cacheManager) { lockManager = cacheManager; } public static CacheManager getCache() { return cacheManager; } public static CacheManager getLockManager() { return lockManager; } public static boolean tryLock(String key) { int expires = 1000 * PropertiesUtil.getInt("redis.lock.expires", 180); return lockManager.setnx(key, Integer.valueOf(expires)); } public static boolean getLock(String key) { return lockManager.lock(key); } public static void unlock(String key) { lockManager.unlock(key); } }
sessionListener.java
package top.ibase4j.core.listener; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.shiro.session.Session; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; public class SessionListener implements org.apache.shiro.session.SessionListener { private Logger logger = LogManager.getLogger(); @Autowired RedisTemplate redisTemplate; public SessionListener() { } public void onStart(Session session) { session.setAttribute("webTheme", "default"); this.logger.info("创建了一个Session连接:[" + session.getId() + "]"); this.redisTemplate.opsForSet().add("S:iBase4J:ALLUSER_NUMBER", new Object[]{session.getId()}); } public void onStop(Session session) { if (this.getAllUserNumber().intValue() > 0) { this.logger.info("销毁了一个Session连接:[" + session.getId() + "]"); } session.removeAttribute("CURRENT_USER"); this.redisTemplate.opsForSet().remove("S:iBase4J:ALLUSER_NUMBER", new Object[]{session.getId()}); } public void onExpiration(Session session) { this.onStop(session); } public Integer getAllUserNumber() { return this.redisTemplate.opsForSet().size("S:iBase4J:ALLUSER_NUMBER").intValue(); } }4)自定义 Real
package org.ibase4j.core.shiro; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.shiro.SecurityUtils; 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.UsernamePasswordToken; 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.ibase4j.model.SysSession; import org.ibase4j.model.SysUser; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.session.data.redis.RedisOperationsSessionRepository; import top.ibase4j.core.base.BaseProvider; import top.ibase4j.core.base.Parameter; import top.ibase4j.core.util.WebUtil; /** * 权限检查类 * * @author ShenHuaJie * @version 2016年5月20日 下午3:44:45 */ public class Realm extends AuthorizingRealm { private final Logger logger = LogManager.getLogger(); @Autowired @Qualifier("sysProvider") protected BaseProvider provider; @Autowired private RedisSessionDAO sessionDAO; @Autowired private RedisOperationsSessionRepository sessionRepository; // 权限 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Long userId = (Long) WebUtil.getCurrentUser(); Parameter parameter = new Parameter("sysAuthorizeService", "queryPermissionByUserId", userId); logger.info("{} execute queryPermissionByUserId start...", parameter.getNo()); List<?> list = provider.execute(parameter).getResultList(); logger.info("{} execute queryPermissionByUserId end.", parameter.getNo()); for (Object permission : list) { if (StringUtils.isNotBlank((String) permission)) { // 添加基于Permission的权限信息 info.addStringPermission((String) permission); } } // 添加用户权限 info.addStringPermission("user"); return info; } // 登录验证 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authcToken; Map<String, Object> params = new HashMap<String, Object>(); params.put("enable", 1); params.put("account", token.getUsername()); Parameter parameter = new Parameter("sysUserService", "queryList", params); logger.info("{} execute sysUserService.queryList start...", parameter.getNo()); List<?> list = provider.execute(parameter).getResultList(); logger.info("{} execute sysUserService.queryList end.", parameter.getNo()); if (list.size() == 1) { SysUser user = (SysUser) list.get(0); StringBuilder sb = new StringBuilder(100); for (int i = 0; i < token.getPassword().length; i++) { sb.append(token.getPassword()[i]); } if (user.getPassword().equals(sb.toString())) { WebUtil.saveCurrentUser(user.getId()); saveSession(user.getAccount(), token.getHost()); AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getAccount(), user.getPassword(), user.getUserName()); return authcInfo; } logger.warn("USER [{}] PASSWORD IS WRONG: {}", token.getUsername(), sb.toString()); return null; } else { logger.warn("No user: {}", token.getUsername()); return null; } } /** 保存session */ private void saveSession(String account, String host) { // 踢出用户 SysSession record = new SysSession(); record.setAccount(account); Parameter parameter = new Parameter("sysSessionService", "querySessionIdByAccount", record); logger.info("{} execute querySessionIdByAccount start...", parameter.getNo()); List<?> sessionIds = provider.execute(parameter).getResultList(); logger.info("{} execute querySessionIdByAccount end.", parameter.getNo()); Subject currentUser = SecurityUtils.getSubject(); Session session = currentUser.getSession(); String currentSessionId = session.getId().toString(); if (sessionIds != null) { for (Object sessionId : sessionIds) { record.setSessionId((String) sessionId); parameter = new Parameter("sysSessionService", "deleteBySessionId", record); logger.info("{} execute deleteBySessionId start...", parameter.getNo()); provider.execute(parameter); logger.info("{} execute deleteBySessionId end.", parameter.getNo()); if (!currentSessionId.equals(sessionId)) { sessionDAO.delete((String) sessionId); // sessionRepository.delete((String) sessionId); // sessionRepository.cleanupExpiredSessions(); } } } // 保存用户 record.setSessionId(currentSessionId); record.setIp(StringUtils.isBlank(host) ? session.getHost() : host); record.setStartTime(session.getStartTimestamp()); parameter = new Parameter("sysSessionService", "update", record); logger.info("{} execute sysSessionService.update start...", parameter.getNo()); provider.execute(parameter); logger.info("{} execute sysSessionService.update end.", parameter.getNo()); } }