關於什么是Shiro,可以查看這篇文章http://www.cnblogs.com/Laymen/articles/6117751.html
一、添加maven依賴
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.3</version> </dependency>
如果嫌麻煩可以直接添加shiro-all的依賴
二、web.xml配置Shiro的過濾器
要讓shiro攔截web的所有請求那么需要我們在web.xml中配置Shrio和web項目整合提供的filter,配置如下:
<!-- 配置Shiro過濾器,先讓Shiro過濾系統接收到的請求 --> <!-- 這里filter-name必須對應applicationContext.xml中定義的<bean id="shiroFilter"/> --> <!-- 使用[/*]匹配所有請求,保證所有的可控請求都經過Shiro的過濾 --> <!-- 通常會將此filter-mapping放置到最前面(即其他filter-mapping前面),以保證它是過濾器鏈中第一個起作用的 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 該值缺省為false,表示生命周期由SpringApplicationContext管理,設置為true則表示由ServletContainer管理 --> <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>
三、Application-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"> <util:properties id="securityCode" location="classpath:config/security-management.properties"/> <bean id="jdbcRealm" class="com.layman.study.core.shiro.realm.LaymanJdbcRealm"> <property name="permissionsLookupEnabled" value="true"/> <property name="name" value="jdbcRealm"/> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="storedCredentialsHexEncoded" value="true"/> <property name="hashAlgorithmName" value="MD5"/> </bean> </property> <property name="authorizationCachingEnabled" value="true"/> <property name="authorizationCacheName" value="shiro_authorization_cache"/> </bean> <bean id="customAuthorizationFilter" class="com.layman.study.core.shiro.filter.CustomAuthorizationFilter"> <property name="ignoreList"> <list> <value>/</value> <value>/login</value> <value>/logout</value> <value>/index</value> <value>/user/register</value> </list> </property> </bean> <!-- Shiro默認會使用Servlet容器的Session,可通過sessionMode屬性來指定使用Shiro原生Session --> <!-- 即<property name="sessionMode" value="native"/>,詳細說明見官方文檔 --> <!-- 這里主要是設置自定義的單Realm應用,若有多個Realm,可使用'realms'屬性代替 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="jdbcRealm"/> <property name="cacheManager"> <bean class="com.layman.study.core.shiro.cache.CustomCacheManager"/> </property> <property name="sessionManager"> <bean class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="deleteInvalidSessions" value="true"/> <property name="sessionDAO"> <bean class="com.layman.study.core.shiro.session.CustomSessionDao"/> </property> </bean> </property> </bean> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login"/> <property name="successUrl" value="/index"/> <property name="unauthorizedUrl" value="/static/page/404.html"/> <property name="filters"> <util:map> <entry key="customAuthorizationFilter" value-ref="customAuthorizationFilter"/> </util:map> </property> <property name="filterChainDefinitionMap"> <map> <entry key="/index" value="authc"/> <entry key="/**" value="customAuthorizationFilter"/> </map> </property> </bean> <!-- 保證實現了Shiro內部lifecycle函數的bean執行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> </beans>
bean的id為shiroFilter的就是web.xml中代理filter需要的spring bean,這個bean中可以看到:
1.loginUrl:指定了當攔截到請求時如果沒有登錄那么就會跳轉到這個屬性指定的地址讓用戶進行登錄操作。
2.successUrl:指定了用戶登錄成功后跳轉的地址
3.unauthorizedUrl:用戶的請求被判斷為沒有權限時會跳轉到這個屬性指定的頁面
4.filters:指定過濾器鏈,可以是默認提供的過濾器也可以指定自定義的過濾器,在這里我指定的是自定義的過濾器com.layman.study.core.shiro.filter.CustomAuthorizationFilter
5.filterChainDefinitionMap:指定過濾器攔截的urlpatten,<entry key="/index" value="authc"/>這句就是使用了Shiro默認給我們提供的一個過濾器org.apache.shiro.web.filter.authc.FormAuthenticationFilter(用戶訪問/index這個路勁時是需要登錄的)<entry key="/**" value="customAuthorizationFilter"/>定義了我們自定義過濾器攔截所有的請求。
Shrio為我們提供的默認過濾器:
/**
* Shiro-1.2.2內置的FilterChain
* @see =========================================================================================================
* @see 1)Shiro驗證URL時,URL匹配成功便不再繼續匹配查找(所以要注意配置文件中的URL順序,尤其在使用通配符時)
* @see 故filterChainDefinitions的配置順序為自上而下,以最上面的為准
* @see 2)當運行一個Web應用程序時,Shiro將會創建一些有用的默認Filter實例,並自動地在[main]項中將它們置為可用
* @see 自動地可用的默認的Filter實例是被DefaultFilter枚舉類定義的,枚舉的名稱字段就是可供配置的名稱
* @see anon---------------org.apache.shiro.web.filter.authc.AnonymousFilter
* @see authc--------------org.apache.shiro.web.filter.authc.FormAuthenticationFilter
* @see authcBasic---------org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
* @see logout-------------org.apache.shiro.web.filter.authc.LogoutFilter
* @see noSessionCreation--org.apache.shiro.web.filter.session.NoSessionCreationFilter
* @see perms--------------org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter
* @see port---------------org.apache.shiro.web.filter.authz.PortFilter
* @see rest---------------org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
* @see roles--------------org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
* @see ssl----------------org.apache.shiro.web.filter.authz.SslFilter
*@see user---------------org.apache.shiro.web.filter.authz.UserFilter
* @see =========================================================================================================
* @see 3)通常可將這些過濾器分為兩組
* @see anon,authc,authcBasic,user是第一組認證過濾器
* @see perms,port,rest,roles,ssl是第二組授權過濾器
* @see 注意user和authc不同:當應用開啟了rememberMe時,用戶下次訪問時可以是一個user,但絕不會是authc,因為authc是需要重新認證的
* @see user表示用戶不一定已通過認證,只要曾被Shiro記住過登錄狀態的用戶就可以正常發起請求,比如rememberMe
* @see 說白了,以前的一個用戶登錄時開啟了rememberMe,然后他關閉瀏覽器,下次再訪問時他就是一個user,而不會authc
* @see ==========================================================================================================
* @see 4)舉幾個例子
* @see /admin=authc,roles[admin] 表示用戶必需已通過認證,並擁有admin角色才可以正常發起'/admin'請求
* @see /edit=authc,perms[admin:edit] 表示用戶必需已通過認證,並擁有admin:edit權限才可以正常發起'/edit'請求
* @see /home=user 表示用戶不一定需要已經通過認證,只需要曾經被Shiro記住過登錄狀態就可以正常發起'/home'請求
* @see ==========================================================================================================
* @see 5)各默認過濾器常用如下(注意URL Pattern里用到的是兩顆星,這樣才能實現任意層次的全匹配)
* @see /admins/**=anon 無參,表示可匿名使用,可以理解為匿名用戶或游客
* @see /admins/user/**=authc 無參,表示需認證才能使用
* @see /admins/user/**=authcBasic 無參,表示httpBasic認證
* @see /admins/user/**=user 無參,表示必須存在用戶,當登入操作時不做檢查
* @see /admins/user/**=ssl 無參,表示安全的URL請求,協議為https
* @see /admins/user/**=perms[user:add:*]
* @see 參數可寫多個,多參時必須加上引號,且參數之間用逗號分割,如/admins/user/**=perms["user:add:*,user:modify:*"]
* @see 當有多個參數時必須每個參數都通過才算通過,相當於isPermitedAll()方法
* @see /admins/user/**=port[8081]
* @see 當請求的URL端口不是8081時,跳轉到schemal://serverName:8081?queryString
* @see 其中schmal是協議http或https等,serverName是你訪問的Host,8081是Port端口,queryString是你訪問的URL里的?后面的參數
* @see /admins/user/**=rest[user]
* @see 根據請求的方法,相當於/admins/user/**=perms[user:method],其中method為post,get,delete等
* @see /admins/user/**=roles[admin]
* @see 參數可寫多個,多個時必須加上引號,且參數之間用逗號分割,如/admins/user/**=roles["admin,guest"]
* @see 當有多個參數時必須每個參數都通過才算通過,相當於hasAllRoles()方法
com.layman.study.core.shiro.filter.CustomAuthorizationFilter自定義過濾器源碼:
public class CustomAuthorizationFilter extends AuthorizationFilter { private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthorizationFilter.class); private static final String AJAX_REQUEST = "XMLHttpRequest"; private List<String> ignoreList; public void setIgnoreList(List<String> ignoreList) { this.ignoreList = ignoreList; } @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { String path = getPathWithinApplication(request); //去除.json的后綴 if (path.endsWith(".json")) { path = path.substring(0, path.length() - 5); } //忽略(通過)特定后綴的訪問 String ext = getExt(path); if (ext != null && !ext.equals(".json")) { return true; } if (!CollectionUtils.isEmpty(ignoreList) && ignoreList.contains(path)) { return true; } //url改寫 如/site/add 改為/site:add,就是把后面的操作(方法)區分出來 int i = path.lastIndexOf('/'); if (i > 0) { path = path.substring(0, i) + ":" + path.substring(i + 1, path.length()); } else if (i == 0) { path = "/:" + path.substring(1, path.length()); } //進行權限驗證 Subject subject = getSubject(request, response); boolean isPermitted = false; try { isPermitted = subject.isPermitted(path); } catch (Exception e) { LOGGER.error("判斷權限出錯:{}", e); } LOGGER.info(path + ":" + isPermitted); return isPermitted; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException { Subject subject = getSubject(request, response); if (StringUtils.isEmpty(subject.getPrincipal())) { String header = WebUtils.toHttp(request).getHeader("x-requested-with"); //當shiro的session超時時 如果用戶發起了ajax請求這個時候頁面並沒有跳轉到我們的配置的登錄頁面, // 所以在后端判斷了下如果登錄超時並是ajax請求就發送一個錯誤碼,在頁面使用全局ajax配置判斷返回碼進行跳轉操作 if (AJAX_REQUEST.equals(header)) { WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED); } else { saveRequestAndRedirectToLogin(request, response); } } else { String unauthorizedUrl = getUnauthorizedUrl(); String path = getPathWithinApplication(request); //如果以.json的形式訪問 則返回.json形式的提醒 if (path.endsWith(".json")) { unauthorizedUrl += ".json"; } if (org.apache.shiro.util.StringUtils.hasText(unauthorizedUrl)) { WebUtils.issueRedirect(request, response, unauthorizedUrl); } else { WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED); } } return false; } /** * 獲取后綴 .js .css等 * * @param path * @return */ private String getExt(String path) { if (path != null) { int index = path.lastIndexOf("."); if (index >= 0) { return path.substring(index, path.length()); } } return null; } }
過濾器攔截到請求后會委托給SecurityManager進行權限驗證,SecurityManager配置:
<!-- Shiro默認會使用Servlet容器的Session,可通過sessionMode屬性來指定使用Shiro原生Session --> <!-- 即<property name="sessionMode" value="native"/>,詳細說明見官方文檔 --> <!-- 這里主要是設置自定義的單Realm應用,若有多個Realm,可使用'realms'屬性代替 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="jdbcRealm"/> <property name="cacheManager"> <bean class="com.layman.study.core.shiro.cache.CustomCacheManager"/> </property> <property name="sessionManager"> <bean class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="deleteInvalidSessions" value="true"/> <property name="sessionDAO"> <bean class="com.layman.study.core.shiro.session.CustomSessionDao"/> </property> </bean> </property> </bean>
自定義realm
在進行權限驗證的時候會通過realm去查詢身份和權限信息,這里我使用了一個自定義的realm(com.layman.study.core.shiro.realm.LaymanJdbcRealm)去mysql中查詢用戶身份信息和權限信息,relam配置如下:
<bean id="jdbcRealm" class="com.layman.study.core.shiro.realm.LaymanJdbcRealm"> <property name="permissionsLookupEnabled" value="true"/> <property name="name" value="jdbcRealm"/> <property name="credentialsMatcher"> <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="storedCredentialsHexEncoded" value="true"/> <property name="hashAlgorithmName" value="MD5"/> </bean> </property> <property name="authorizationCachingEnabled" value="true"/> <property name="authorizationCacheName" value="shiro_authorization_cache"/> </bean>
1.credentialsMatcher:mysql中用戶信息的密碼我進行了一次MD5散列算法,不是保存的明文,所以在realm中我們可以通過這個屬性指定我們需要的散列算法,同時在進行身份驗證的時候我們還可以指定參與散列算法的salt,這個值是用戶注冊時一起保存在數據庫中的。shiro在身份驗證的時候對salt的操作是使用ByteSource類將salt轉會為byte[],所以在進行散列算法的時候需要使用shiro提供的算法類在提供了salt時進行相同的處理,用戶密碼散列算法如下:
//使用shiro提供的散列算法類進行散列計算 String pwd = new Md5Hash(user.getPassword(), user.getSalt(), 1).toString(); /** * 隨機產生的salt * * @return */ private String getRandomSalt() { //shiro提供的一個隨機數生存類 RandomNumberGenerator gen = new SecureRandomNumberGenerator(); ByteSource salt = gen.nextBytes();//返回的是一個SimpleByteSource實例 return salt.toString();//SimpleByteSource類覆寫了toString方法,其實質就是調用了它的toBase64()方法 }
或者使用SimpleHashRequest類,指定需要的散列算法
2.authorizationCacheName:指定權限緩存名稱,shiro針對用戶每個請求都會去判斷是否有權限,如果使用了緩存,CacheManager會調用getCache(String name)(方法這里的name值就是authorizationCacheName的值)獲得一個Cache實例進行緩存操作。
LaymanJdbcRealm源碼
@Component public class LaymanJdbcRealm extends JdbcRealm { @Autowired private SysUserService userService; @Autowired private SysPermissionService permissionService; @Autowired private SysRoleService roleService; @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String userName = (String) token.getPrincipal(); SysUser user = userService.getUserByName(userName); if (null == user || null == user.getPassword()) { return null; } return new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), user.getNickName()); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { Set<String> roles = new HashSet<String>(); Set<String> permissions = new HashSet<String>(); String userName = (String) principals.getPrimaryPrincipal(); SysUser user = userService.getUserByName(userName); List<SysRole> roleList = roleService.getRoleList(user.getId()); if (null != user) { for (SysRole sysRole : roleList) { roles.add(sysRole.getId().toString()); List<SysPermission> permissionList = permissionService.getPermissionListByRoleId(sysRole.getId()); for (SysPermission permission : permissionList) { permissions.add(permission.getPermissionCode()); } } } SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.setStringPermissions(permissions); return authorizationInfo; } }
自定義緩存(Cache)策略(使用的redis作為緩存)
com.layman.study.core.shiro.cache.CustomCacheManager源碼:
public class CustomCacheManager implements CacheManager { private static final Logger LOGGER = LoggerFactory.getLogger(CustomCacheManager.class); @Autowired private RedisTemplate redisTemplate; @Value("#{securityCode['security.management.code']}") private String securityManagementCode; private static ConcurrentMap<String, CustomRedisCache> cacheMap = new ConcurrentHashMap<String, CustomRedisCache>(); public <K, V> Cache<K, V> getCache(String name) throws CacheException { CustomRedisCache cache; cache = cacheMap.get(name); if (null != cache) { LOGGER.info("從cacheMap中根據名字:{},獲取到了cache", name); } cache = new CustomRedisCache(redisTemplate, securityManagementCode); cacheMap.put(name, cache); return cache; } }
CustomCacheManager在spring實例化時就會根據realm中配置的信息調用getCache(String name)方法查詢Cache實例,如果沒有獲取到就創建一個Cache返回並保存到自己的ConcurrentMap中,Cache實例才是我們對權限信息進行緩存操作的具體實現,這里我使用的是redis作為緩存容器
那么就需要自定一個Cache對redis進行操作,CustomRedisCache源碼如下:
public class CustomRedisCache<K, V> implements Cache<K, V> { private static final Logger LOGGER = LoggerFactory.getLogger(CustomRedisCache.class); private static final String SHIRO_CACHE_PREFIX = "shiro-cache:"; private String SECURITY_MANAGEMENT_CODE = "default"; private RedisTemplate redisTemplate; public CustomRedisCache() { } public CustomRedisCache(RedisTemplate redisTemplate, String securityManagementCode) { this.redisTemplate = redisTemplate; this.SECURITY_MANAGEMENT_CODE = securityManagementCode; } @Override public V get(K key) throws CacheException { if (null == key) { return null; } try { V result = (V) redisTemplate.opsForValue().get((K) (SHIRO_CACHE_PREFIX + key)); LOGGER.info("根據key[{}]獲得緩存{}", key, result); return result; } catch (Exception e) { LOGGER.error("獲取shiro緩存錯誤:", e); throw new CacheException(e); } } @Override public V put(K key, V value) throws CacheException { try { redisTemplate.opsForValue().set((K) (SHIRO_CACHE_PREFIX + key), value, 10, TimeUnit.MINUTES); LOGGER.info("存儲shiro的緩存信息,key:{},value:{}", key, value); return value; } catch (Exception e) { LOGGER.error("存儲shiro緩存發生錯誤key={},value={},error=", key, value, e); throw new CacheException(e); } } @Override public V remove(K key) throws CacheException { try { V deleteObj = get(key); redisTemplate.delete((K) (SHIRO_CACHE_PREFIX + key)); LOGGER.info("刪除shrio緩存key={},value={}", key, deleteObj); return deleteObj; } catch (Exception e) { LOGGER.error("刪除shiro緩存發生錯誤:{}", e); throw new CacheException(e); } } @Override public void clear() throws CacheException { try { redisTemplate.delete(SHIRO_CACHE_PREFIX + "*"); LOGGER.info("成功清楚shiro所有緩存"); } catch (Exception e) { LOGGER.error("刪除所有shiro緩存出錯:{}", e); throw new CacheException(e); } } @Override public int size() { try { Long size = redisTemplate.opsForValue().size(SHIRO_CACHE_PREFIX + "*"); LOGGER.info("shiro緩存大小:{}", size); return size.intValue(); } catch (Exception e) { LOGGER.error("獲取食肉緩存大小錯誤:{}", e); throw new CacheException(e); } } @Override public Set<K> keys() { try { Set<K> keys = redisTemplate.keys((K) (SHIRO_CACHE_PREFIX + "*")); if (CollectionUtils.isEmpty(keys)) { return Collections.emptySet(); } else { Set<K> newKeys = new HashSet<K>(); for (K key : keys) { newKeys.add(key); } LOGGER.info("獲取shiro緩存的所有key:{}", newKeys); return newKeys; } } catch (Exception e) { LOGGER.error("獲取shiro緩存的keys錯誤:{}", e); throw new CacheException(e); } } @Override public Collection<V> values() { try { Set<K> keys = redisTemplate.keys(SHIRO_CACHE_PREFIX + "*"); if (!CollectionUtils.isEmpty(keys)) { List<V> values = new ArrayList<V>(keys.size()); for (K key : keys) { V value = get(key); if (value != null) { values.add(value); } } LOGGER.info("獲取shiro緩存的所有value:{}", values); return Collections.unmodifiableList(values); } else { return Collections.emptyList(); } } catch (Throwable t) { LOGGER.error("獲取shiro緩存的values錯誤:{}", t); throw new CacheException(t); } } }
自定義SessionDao
Shiro提供安全框架界獨一無二的東西:一個完整的企業級Session 解決方案,可以為任意的應用提供session支持,包括web和非web應用,並且無需部署你的應用程序到Web 容器或使用EJB容器。
關於SessionManager,SessionManager是用來管理Session的組件,包括:創建,刪除,inactivity(失效)及驗證,等等。SessionManager 也是一個由SecurityManager 維護的頂級組件。shiro提供了默認的SessionManager實現,一般沒有必要自定義這個。
但是可以通過設置他的屬性來控制Session管理策略:
(1)設置Sessioin的過期時間;Shiro 的SessionManager 實現默認是30 分鍾會話超時。你可以設置SessionManager 默認實現的globalSessionTimeout 屬性來為所有的會話定義默認的超時時間。
(2)Sessioin的事件監聽;你可以實現SessionListener 接口(或擴展易用的SessionListenerAdapter)並與相應的會話操作作出反應。
。。。。。。。等
關於SessionDAO,每當一個會話被創建或更新時,它的數據需要持久化到一個存儲位置以便它能夠被稍后的應用程序訪問,實現這個功能的組件就是SessionDAO。你能夠實現該接口來與你想要的任何數據存儲進行通信。這意味着你的會話數據可以駐留在內存中,文件系統,關系數據庫或NoSQL 的數據存儲,或其他任何你需要的位置。
com.layman.study.core.shiro.session.CustomSessionDao源碼:
public class CustomSessionDao extends AbstractSessionDAO { private static final Logger LOGGER = LoggerFactory.getLogger(CustomSessionDao.class); private static final String SHIRO_SESSION_PREFIX = "shiro-session:"; @Value("#{securityCode['security.management.code']}") private String securityManagementCode; @Autowired private RedisTemplate<String, byte[]> redisTemplate; @Override protected Serializable doCreate(Session session) { Serializable sessionId = this.getSessionIdGenerator().generateId(session); this.assignSessionId(session, sessionId); String key = this.buildRedisKey(sessionId); redisTemplate.opsForValue().set(key, SerializationUtils.serialize(session), session.getTimeout(), TimeUnit.MILLISECONDS); return sessionId; } @Override protected Session doReadSession(Serializable serializable) { if (null == serializable) { return null; } String key = this.buildRedisKey(serializable); byte[] value = redisTemplate.opsForValue().get(key); return (Session) SerializationUtils.deserialize(value); } @Override public void update(Session session) throws UnknownSessionException { redisTemplate.opsForValue().set(this.buildRedisKey(session.getId()), SerializationUtils.serialize(session), session.getTimeout(), TimeUnit.MILLISECONDS); } @Override public void delete(Session session) { redisTemplate.delete(this.buildRedisKey(session.getId())); } @Override public Collection<Session> getActiveSessions() { Set<Session> sessions = new HashSet<Session>(); Set<String> keys = redisTemplate.keys(this.buildRedisKey("*")); if (!CollectionUtils.isEmpty(keys)) { for (String key : keys) { Session s = (Session) SerializationUtils.deserialize(redisTemplate.opsForValue().get(key)); sessions.add(s); } } return sessions; } private String buildRedisKey(Serializable sessionId) { return SHIRO_SESSION_PREFIX + securityManagementCode + ":" + sessionId; } }
開啟注解
<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>
@RequiresPermissions() @RequiresAuthentication() @RequiresRoles() @RequiresUser() @RequiresGuest()的使用
引入Tag
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
guest 標簽將顯示它包含的內容,僅當當前的Subject 被認為是‘guest’時。‘guest’是指沒有身份ID 的任何Subject。也就是說,我們並不知道用戶是誰,因為他們沒有登錄並且他們沒有在上一次的訪問中被記住(RememberMe 服務), guest 標簽與user 標簽邏輯相反。例子:
<shiro:guest> Hi there! Please <a href="login.jsp">Login</a> or <a href="signup.jsp">Signup</a>today! </shiro:guest>
user 標簽將顯示它包含的內容,僅當當前的Subject 被認為是‘user’時。‘user’在上下文中被定義為一個已知身份ID的Subject,或是成功通過身份驗證及通過‘RememberMe’服務的。請注意這個標簽在語義上與authenticated 標簽是不同的,authenticated 標簽更為嚴格。usre 標簽與guest 標簽邏輯相反。
僅僅只當當前用戶在當前會話中成功地通過了身份驗證authenticated 標簽才會顯示包含的內容。它比‘user’標簽更為嚴格。它在邏輯上與‘notAuthenticated’標簽相反。
notAuthenticated 標簽將會顯示它所包含的內容,如果當前Subject 還沒有在其當前會話中成功地通過驗證。
principal 標簽將會輸出Subject 的主體(標識屬性)或主要的屬性。
hasRole 標簽將會顯示它所包含的內容,僅當當前Subject 被分配了具體的角色。 hasRole 標簽與lacksRole 標簽邏輯相反。 例如:
<shiro:hasRole name="administrator"> <a href="admin.jsp">Administer the system</a> </shiro:hasRole>
lacksRole 標簽將會顯示它所包含的內容,僅當當前Subject 未被分配具體的角色
hasAnyRole 標簽將會顯示它所包含的內容,如果當前的Subject 被分配了任意一個來自於逗號分隔的角色名列表中的具體角色。例如:
<shiro:hasAnyRoles name="developer, project manager, administrator"> You are either a developer, project manager, or administrater. </shiro:hasAnyRoles>
hasPermission 標簽將會顯示它所包含的內容,僅當當前Subject“擁有”(蘊含)特定的權限。也就是說,用戶具有特定的能力。hasPermission 標簽與lacksPermission 標簽邏輯相反。例如:
<shiro:hasPermission name="user:create"> <a href="createUser.jsp">Create a new User</a> </shiro:hasPermission>
lacksPermission 標簽將會顯示它所包含的內容,僅當當前Subject 沒有擁有(蘊含)特定的權限。也就是說,用戶沒有特定的能力。
