上些章節(jié)我們都學習了shiro中的各種實際應用技巧,今天我想講的是如何動態(tài)控制頁面節(jié)點權限,相信這個控制對于很多玩權限的人來說都是一個比較頭痛的,
因為這實在不怎么好統(tǒng)一控制的,現(xiàn)在我來展示下我通過shiro是如何實現(xiàn)的,算是拋磚引玉,希望大家有更好的解決方案,可以相互學習;下面就直接進入主題不啰嗦了
之前我們已經(jīng)學習了如何在項目啟動的時候加載所有的資源角色,包括第三方資源,下面我再帖上加長加粗版的代碼方便說明
- package com.silvery.security.shiro.service.impl;
-
- import java.text.MessageFormat;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
-
- import org.apache.shiro.cache.Cache;
- import org.springframework.beans.factory.annotation.Autowired;
-
- import com.silvery.project.cms.model.Authority;
- import com.silvery.project.cms.model.Permission;
- import com.silvery.project.cms.service.PermissionService;
- import com.silvery.project.cms.vo.PermissionVo;
- import com.silvery.security.shiro.cache.SimpleMapCache;
- import com.silvery.security.shiro.cache.extend.SimpleCacheManager;
- import com.silvery.security.variable.Const;
-
- /**
- *
- * 加載第三方角色資源配置服務類
- *
- * @author shadow
- *
- */
- public class SimpleFilterChainDefinitionsService extends AbstractFilterChainDefinitionsService {
-
- @Autowired
- private SimpleCacheManager simpleCacheManager;
-
- @Autowired
- private PermissionService permissionService;
-
- @Override
- public Map<String, String> initOtherPermission() {
- return converResultMap(initOperation());
- }
-
- @SuppressWarnings("unchecked")
- private Map<Object, Object> initOperation() {
-
- Map<Object, Object> resultMap = new HashMap<Object, Object>();
-
- // 加載數(shù)據(jù)庫所有資源
- PermissionVo vo = new PermissionVo();
- List<Permission> permissions = (List<Permission>) permissionService.query(vo).getValue();
-
- List<Authority> authorities = null;
-
- for (Permission permission : permissions) {
-
- // 遍歷查詢當前資源的配置角色
- vo.setId(permission.getId());
- authorities = (List<Authority>) permissionService.query4authority(vo).getValue();
-
- // 組裝角色集合
- Set<String> authoritySet = getPermissionSet(authorities);
-
- if (authoritySet.isEmpty()) {
- continue;
- }
- if (permission.getType() == 1) {
- // 請求路徑資源處理
- resultMap.put(permission.getContent(), MessageFormat.format(SHIRO_AUTHORITY_FORMAT, authoritySet));
- } else {
- // 元素資源處理
- Map<Object, Object> map = new HashMap<Object, Object>(1);
- map.put(Const.OTHER_PERMISSSION_CACHE_NAME, authoritySet);
- Cache<Object, Object> cache = new SimpleMapCache(Const.OTHER_PERMISSSION_CACHE_NAME, map);
- simpleCacheManager.createCache(Const.OTHER_PERMISSSION_CACHE_NAME + "_" + permission.getId(), cache);
- }
- }
-
- return resultMap;
- }
-
- /** 獲取角色名稱集合 */
- private Set<String> getPermissionSet(List<Authority> authorities) {
- Set<String> authoritieSet = new HashSet<String>(authorities.size());
- for (Authority authority : authorities) {
- authoritieSet.add(authority.getContent());
- }
- return authoritieSet;
- }
-
- /** 泛型Object轉(zhuǎn)換String */
- private Map<String, String> converResultMap(Map<Object, Object> map) {
- Map<String, String> resultMap = new HashMap<String, String>(map.size());
- for (Map.Entry<Object, Object> entry : map.entrySet()) {
- resultMap.put(entry.getKey().toString(), entry.getValue().toString());
- }
- return resultMap;
- }
-
- }
1. 加載所有資源,包括請求URL,元素節(jié)點等數(shù)據(jù),我這里為了演示,沒有分那么細就只有兩種
2. 請求URL的直接放到框架中,形式如/user/list.do*=role[root,user],跟我們shiro.xml配置的一樣
3. 元素節(jié)點則相應放到以鍵值對的形式放到緩存,以節(jié)點的編號組合成key保證可以找到這個緩存對,值是相應的角色集合
我們的緩存就存在各個資源所需要的角色集合, 加載數(shù)據(jù)步驟已經(jīng)完畢了下面看看我們是怎么應用的
首先我是使用springMVC+freemarker作為展現(xiàn)層,具體怎么配置整合我也不說,百度一堆,直接看我帖代碼說明
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>歡迎主頁</title>
- </head>
- <body>
- ${username!"游客"}, 歡迎您的訪問! <br />
- <hr />
- 可選操作:
- <#if username??>
- <@sec id="2" body="<a href='/cms/user/page.do'>用戶列表</a> " /><a href="/cms/authority/page.do">權限列表</a> <a href="/cms/permission/page.do">資源列表</a> <a href="/cms/logout.do">注銷退出</a>
- <#else>
- <a href="#" onclick="top.location.href='/cms/logout.do';">前往登錄</a>
- </#if>
- <hr />
- </body>
- </html>
很明顯看到我的頁面有一個@sec的標簽,然后有兩個參數(shù),一個id,一個是body,至于id則是你資源的編號,body是需要元素節(jié)點內(nèi)容
然后我們怎么通過這個標簽來判定是否在頁面渲染body的節(jié)點內(nèi)容呢?
下面我們看看這個@sec的實現(xiàn)
- package com.silvery.core.freemarker;
-
- import java.io.IOException;
- import java.util.Map;
- import java.util.Set;
-
- import org.apache.shiro.SecurityUtils;
- import org.apache.shiro.cache.Cache;
- import org.apache.shiro.subject.Subject;
- import org.springframework.beans.factory.annotation.Autowired;
-
- import com.silvery.security.shiro.cache.extend.SimpleCacheManager;
- import com.silvery.security.variable.Const;
-
- import freemarker.core.Environment;
- import freemarker.template.TemplateDirectiveBody;
- import freemarker.template.TemplateDirectiveModel;
- import freemarker.template.TemplateException;
- import freemarker.template.TemplateModel;
-
- /**
- *
- * FreeMarker自定義標簽,節(jié)點權限控制
- *
- * @author shadow
- *
- */
- public class SecurityTag implements TemplateDirectiveModel {
-
- @Autowired
- private SimpleCacheManager simpleCacheManager;
-
- @SuppressWarnings("unchecked")
- public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody directiveBody)
- throws TemplateException, IOException {
-
- Object id = params.get("id");
- Object body = params.get("body");
-
- validate(id, body);
-
- if (hasRole(id)) {
- env.getOut().write(body.toString());
- } else {
- env.getOut().write("");
- }
-
- }
-
- private void validate(Object id, Object body) throws TemplateException {
- if (id == null || id.toString().trim().equals("")) {
- throw new TemplateException("參數(shù)[id]不能為空", null);
- }
- if (body == null) {
- throw new TemplateException("參數(shù)[body]不能為空", null);
- }
- }
-
- @SuppressWarnings("unchecked")
- private boolean hasRole(Object id) {
- Cache<Object, Object> cache = simpleCacheManager.getCache(Const.OTHER_PERMISSSION_CACHE_NAME + "_" + id);
- if (cache == null) {
- return false;
- } else {
- Object obj = cache.get(Const.OTHER_PERMISSSION_CACHE_NAME);
- if (obj == null) {
- return false;
- }
- Set<String> authoritySet = (Set<String>) obj;
- Subject subject = SecurityUtils.getSubject();
- for (String authority : authoritySet) {
- if (subject.hasRole(authority)) {
- return true;
- }
- }
- }
- return false;
- }
-
- }
很清晰地看到,我們是用到之前的那個資源角色緩存,通過判定緩存中存在的角色與當前shiro認證用戶擁有的角色匹配,如存在任意相同角色則渲染相應節(jié)點
如Subject擁有角色[root,user],而x編號資源擁有[user,test]角色,很明顯有交集會認證通過,反之則直接渲染空字符
大概流程就是這樣,有的人可能會問,如何配置這個tag實現(xiàn)類呢?我帖下spring-mvc.xml的freemarker配置
- <!-- FreeMarker配置 -->
- <bean id="freemarkerConfig"
- class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
- <property name="templateLoaderPath" value="/WEB-INF/ftl/" />
- <property name="defaultEncoding" value="UTF-8" />
- <property name="freemarkerVariables">
- <map>
- <entry key="sec">
- <bean class="com.silvery.core.freemarker.SecurityTag">
- </bean>
- </entry>
- </map>
- </property>
- <property name="freemarkerSettings">
- <props>
- <prop key="template_update_delay">10</prop>
- <prop key="locale">zh_CN</prop>
- <prop key="datetime_format">yyyy-MM-dd HH:mm:ss</prop>
- <prop key="date_format">yyyy-MM-dd</prop>
- <prop key="number_format">#.####</prop>
- </props>
- </property>
- </bean>
相信懂freemarker的人都能看明白,我也不累贅說明;最后再說一句,謝謝大家支持.