shiro安全框架擴展教程--如何動態控制頁面節點元素的權限

         上些章節我們都學習了shiro中的各種實際應用技巧,今天我想講的是如何動態控制頁面節點權限,相信這個控制對於很多玩權限的人來說都是一個比較頭痛的,

因爲這實在不怎麼好統一控制的,現在我來展示下我通過shiro是如何實現的,算是拋磚引玉,希望大家有更好的解決方案,可以相互學習;下面就直接進入主題不囉嗦了


之前我們已經學習瞭如何在項目啓動的時候加載所有的資源角色,包括第三方資源,下面我再帖上加長加粗版的代碼方便說明


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>();

		// 加載數據庫所有資源
		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轉換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,元素節點等數據,我這裏爲了演示,沒有分那麼細就只有兩種

2. 請求URL的直接放到框架中,形式如/user/list.do*=role[root,user],跟我們shiro.xml配置的一樣

3. 元素節點則相應放到以鍵值對的形式放到緩存,以節點的編號組合成key保證可以找到這個緩存對,值是相應的角色集合


我們的緩存就存在各個資源所需要的角色集合, 加載數據步驟已經完畢了下面看看我們是怎麼應用的


首先我是使用springMVC+freemarker作爲展現層,具體怎麼配置整合我也不說,百度一堆,直接看我帖代碼說明


<!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="#" οnclick="top.location.href='/cms/logout.do';">前往登錄</a> 
	</#if>
	<hr />
</body>
</html>

很明顯看到我的頁面有一個@sec的標籤,然後有兩個參數,一個id,一個是body,至於id則是你資源的編號,body是需要元素節點內容


然後我們怎麼通過這個標籤來判定是否在頁面渲染body的節點內容呢?


下面我們看看這個@sec的實現


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自定義標籤,節點權限控制
 * 
 * @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("參數[id]不能爲空", null);
		}
		if (body == null) {
			throw new TemplateException("參數[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認證用戶擁有的角色匹配,如存在任意相同角色則渲染相應節點


如Subject擁有角色[root,user],而x編號資源擁有[user,test]角色,很明顯有交集會認證通過,反之則直接渲染空字符


大概流程就是這樣,有的人可能會問,如何配置這個tag實現類呢?我帖下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的人都能看明白,我也不累贅說明;最後再說一句,謝謝大家支持.
發佈了57 篇原創文章 · 獲贊 211 · 訪問量 67萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章