單擊導航欄上的"角色管理"超連接,跳轉到角色管理界面,在該界面上顯示全部角色,並提供角色的增長和刪除、修改超連接。javascript
流程:單擊增長新角色超連接->Action查詢出全部的權限保存到值棧並轉到添加新角色頁面->填寫表單並提交->Action保存表單->重定向到角色管理Action前端
技術點:表單提交的權限列表時一個整型數組,須要在Action中進行接收並調用相關方法轉換成Rright列表;使用到了一些JQuery技術實現更友好的前端交互。java
JQuery代碼:web
1 <script type="text/javascript"> 2 $().ready(function(){ 3 $("button[id='toRight']").unbind("click"); 4 $("button[id='toRight']").bind("click",function(){ 5 var noneOwnRights=$("select[name='noneOwnRights']"); 6 var ownRights=$("select[name='ownRights']"); 7 $("select[name='ownRights'] option:selected").each(function(){ 8 noneOwnRights.prepend($(this).clone()); 9 $(this).remove(); 10 }); 11 return false; 12 }); 13 $("button[id='toLeft']").unbind("click"); 14 $("button[id='toLeft']").bind("click",function(){ 15 var noneOwnRights=$("select[name='noneOwnRights']"); 16 var ownRights=$("select[name='ownRights']"); 17 $("select[name='noneOwnRights'] option:selected").each(function(){ 18 ownRights.prepend($(this).clone()); 19 $(this).remove(); 20 }); 21 return false; 22 }); 23 $("button[id='allToRight']").unbind("click"); 24 $("button[id='allToRight']").bind("click",function(){ 25 var noneOwnRights=$("select[name='noneOwnRights']"); 26 var ownRights=$("select[name='ownRights']"); 27 $("select[name='ownRights'] option").each(function(){ 28 noneOwnRights.append($(this).clone()); 29 $(this).remove(); 30 }); 31 return false; 32 }); 33 $("button[id='allToLeft']").unbind("click"); 34 $("button[id='allToLeft']").bind("click",function(){ 35 var noneOwnRights=$("select[name='noneOwnRights']"); 36 var ownRights=$("select[name='ownRights']"); 37 $("select[name='noneOwnRights'] option").each(function(){ 38 ownRights.append($(this).clone()); 39 $(this).remove(); 40 }); 41 return false; 42 }); 43 $("#submit").unbind("click"); 44 $("#submit").bind("click",function(){ 45 $("select[name='ownRights'] option").each(function(){ 46 $(this).attr("selected","selected"); 47 }); 48 return true; 49 }); 50 }); 51 </script>
形式和流程和角色受權徹底一致,略。spring
所謂的權限的粗粒度控制指的是改造登錄攔截器使其成爲權限控制攔截器,當用戶訪問某個資源的時候將會根據不一樣的訪問地址判斷是否有權限訪問,若是有權限訪問則放行,不然跳轉到錯誤提示頁。數據庫
權限控制攔截器中判斷權限的流程以前說過了,以下圖所示:express
1 // 驗證是否有權限的驗證方法 2 public static boolean hasRight(String namespace, String actionName, HttpServletRequest request,Action action) { 3 String url = namespace + "/" 4 + (actionName.contains("?") ? actionName.substring(0, actionName.indexOf("?")) : actionName) 5 + ".action"; 6 // TODO 將權限列表放入到ServletContext中的方法 7 HttpSession session = request.getSession(); 8 ServletContext sc = session.getServletContext(); 9 Map<String, Right> allRights = (Map<String, Right>) sc.getAttribute("all_rights_map"); 10 Right right = allRights.get(url); 11 // 若是是公共資源直接方放過 12 if (right == null || right.getCommon()) { 13 // System.out.println("訪問公共資源,即將放行!"); 14 return true; 15 } else { 16 User user = (User) session.getAttribute("user"); 17 // 判斷是否已經登錄 18 if (user == null) { 19 return false; 20 } else { 21 // 若是實現了UserAware接口 22 if (action != null && action instanceof UserAware) { 23 UserAware userAware = (UserAware) action; 24 userAware.setUser(user); 25 } 26 // 若是是超級管理員直接放行 27 if (user.getSuperAdmin()) { 28 return true; 29 // 不然先檢查是否有權限 30 } else { 31 if (user.hasRight(right)) { 32 return true; 33 } else { 34 return false; 35 } 36 } 37 } 38 } 39 }
上面代碼中的粗體部分是獲取放到application做用域中的全部權限Map,key值是url,value值是對應的Right對象。apache
1 package com.kdyzm.struts.interceptors; 2 3 import java.util.Map; 4 5 import javax.servlet.ServletContext; 6 import javax.servlet.http.HttpServletRequest; 7 import javax.servlet.http.HttpSession; 8 9 import org.apache.struts2.ServletActionContext; 10 11 import com.kdyzm.domain.User; 12 import com.kdyzm.domain.security.Right; 13 import com.kdyzm.struts.action.aware.UserAware; 14 import com.kdyzm.utils.ValidateUtils; 15 import com.opensymphony.xwork2.Action; 16 import com.opensymphony.xwork2.ActionInvocation; 17 import com.opensymphony.xwork2.ActionProxy; 18 import com.opensymphony.xwork2.interceptor.Interceptor; 19 20 /** 21 * 只要請求了Action就會默認訪問該攔截器 22 * 登錄攔截器 23 * @author kdyzm 24 * 25 */ 26 public class LoginInterceptor implements Interceptor{ 27 private static final long serialVersionUID = 7321012192261008127L; 28 29 @Override 30 public void destroy() { 31 System.out.println("登陸攔截器被銷燬!"); 32 } 33 34 @Override 35 public void init() { 36 System.out.println("登陸攔截器初始化!"); 37 } 38 /** 39 * 對登陸攔截器進行改造使其成爲權限過濾攔截器 40 */ 41 @SuppressWarnings("unchecked") 42 @Override 43 public String intercept(ActionInvocation invocation) throws Exception { 44 //首先獲取請求的Action的名稱 45 ActionProxy actionProxy=invocation.getProxy(); 46 String namespace=actionProxy.getNamespace(); 47 String actionName=actionProxy.getActionName(); 48 if(namespace==null||"/".equals(namespace)){ 49 namespace=""; 50 } 51 HttpServletRequest request=ServletActionContext.getRequest(); 52 boolean result=ValidateUtils.hasRight(namespace, actionName, request, (Action)invocation.getAction()); 53 if(result==true){ 54 return invocation.invoke(); 55 }else{ 56 return "no_right_error"; 57 } 58 } 59 }
<global-results> <result name="toLoginPage">/index.jsp</result> <!-- 定義全局結果類型,將編輯頁面以後的返回頁面定義爲全局結果類型 --> <result name="toDesignSurveyPageAction" type="redirectAction"> <param name="surveyId">${surveyId}</param> <param name="namespace">/</param> <param name="actionName">SurveyAction_designSurveyPage.action</param> </result> <result name="no_right_error">/error/no_right_error.jsp</result> </global-results>
在權限控制的過程當中會常常須要查詢權限,若是每次都查詢數據庫中會對數據庫形成很大的負擔,最好的方式是將其放到內存,並且使用Map的數據結構更加方便的查詢。數組
將權限集合拿到內存的時機就是tomcat啓動完成以前,這裏藉助spring容器的監聽器實現該功能。tomcat
實現的技術要點:
1.如何獲取application對象,在struts2中經過ServletContextAware接口能夠將ServletContext注入到Action,在這裏因爲spring初始化的時候strus2尚未初始化,因此就不能經過實現struts2的接口來注入application對象了;spring提供了相同的方式注入application對象,注意不要導錯了包,接口名都是ServletContextAware。
2.直接經過註解的方式注入spring容器,在包掃描的規則中添加com.kdyzm.listener。
1 package com.kdyzm.listener; 2 3 import java.util.Collection; 4 import java.util.HashMap; 5 import java.util.Map; 6 7 import javax.annotation.Resource; 8 import javax.servlet.ServletContext; 9 10 import org.springframework.context.ApplicationEvent; 11 import org.springframework.context.ApplicationListener; 12 import org.springframework.context.event.ContextRefreshedEvent; 13 import org.springframework.stereotype.Component; 14 import org.springframework.web.context.ServletContextAware; 15 16 import com.kdyzm.domain.security.Right; 17 import com.kdyzm.service.RightService; 18 19 /** 20 * 初始化權限數據的監聽類 21 * 該監聽器的做用就是將全部的權限放入ServletContext中 22 * Spring容器初始化的時候struts2尚未初始化,因此不能使用struts2的ServletContextAware獲取SerlvetContext對象。 23 * 可是spring提供了相同的機制獲取ServletContext對象,並且使用的方法和接口也是徹底相同。 24 * 這裏還有一個很是重要的東西:注入sc必定在前。 25 * 26 * 直接使用註解注入到spring容器,不須要對配置文件進行修改 27 * @author kdyzm 28 * 29 */ 30 @Component 31 public class InitRightListener implements ApplicationListener,ServletContextAware{ 32 private ServletContext sc; 33 @Resource(name="rightService") 34 private RightService rightService; 35 @Override 36 public void onApplicationEvent(ApplicationEvent event) { 37 //這裏全部的ApplicationContext的事件都會不獲到,因此必須進行判斷已進行分類處理 38 if(event instanceof ContextRefreshedEvent){ 39 Collection<Right> rights=rightService.getAllRights(); 40 Map<String,Right>rightMap=new HashMap<String,Right>(); 41 for(Right right: rights){ 42 System.out.println(right.getRightUrl()+":"+right.getCommon()); 43 rightMap.put(right.getRightUrl(), right); 44 } 45 if(sc!=null){ 46 sc.setAttribute("all_rights_map", rightMap); 47 System.out.println("初始化RightMap成功!"); 48 }else{ 49 System.out.println("ServletContext對象爲空,初始化RightMap對象失敗!"); 50 } 51 } 52 } 53 54 //注入ServletContext 55 @Override 56 public void setServletContext(ServletContext servletContext) { 57 System.out.println("注入ServletContext對象"); 58 this.sc=servletContext; 59 } 60 61 }
ApplicationContext.xml配置文件也須要修改:
1 <context:component-scan 2 base-package="com.kdyzm.dao.impl,com.kdyzm.service.impl,com.kdyzm.struts.action,com.kdyzm.dao.base.impl,com.kdyzm.listener"></context:component-scan>
所謂細粒度控制就是和粗粒度控制相比較而言的,粗粒度控制旨在當用戶訪問了無權限訪問的資源的時候,攔截其訪問;細粒度控制旨在更深一步細化權限控制,不讓用戶有機會訪問無權限訪問的資源,也就是說控制關鍵標籤的顯示,好比超連接、提交按鈕等。
方法就是重寫struts2的標籤類,覆蓋掉struts2提供的class文件,這種方式在tomcat下是沒有問題的,在其它環境下沒有測試,結果未知,最好的方法就是將jar包中對應的class文件剔除,這樣類就惟一了。
必定使用struts2徹底匹配版本的源代碼,不然版本不一樣特別是差別比較大的,很是有可能會出現意料以外的異常。
org.apache.struts2.views.jsp.ui.AnchorTag 對應着<s:a></s:a>
org.apache.struts2.views.jsp.ui.SubmitTag 對應着<s:submit></s:submit>
重寫AnchorTag類比較簡單,只須要重寫doEndTag方法便可,注意,該類有屬性pageContext,能夠直接獲取HttpServletRequest對象;第四個參數爲Action對象,這裏沒有就填寫NULL,Action對象參數的目的是爲了將User對象注入到Action。
1 //a標籤只須要重寫一個方法就行 2 @Override 3 public int doEndTag() throws JspException { 4 if(namespace==null||"/".equals(namespace)){ 5 namespace=""; 6 } 7 if(action==null){ 8 action=""; 9 }else{ 10 if(action.endsWith(".action")){ 11 action=action.substring(0, action.indexOf(".")); 12 } 13 } 14 boolean result=ValidateUtils.hasRight(namespace, action, (HttpServletRequest)pageContext.getRequest(), null); 15 // System.out.println("即將訪問"+namespace+action); 16 if(result==true){ 17 // System.out.println("有權限,即將放行!"); 18 return super.doEndTag(); 19 }else{ 20 // System.out.println("沒有權限,即將跳過標籤體!"); 21 return SKIP_BODY; 22 } 23 }
完整代碼:
1 /* 2 * $Id: AnchorTag.java 768855 2009-04-27 02:09:35Z wesw $ 3 * 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 * 21 * 對應着<s:a>標籤,重寫該類中的某個方法便可完成對權限細粒度的劃分 22 */ 23 24 package org.apache.struts2.views.jsp.ui; 25 26 import javax.servlet.http.HttpServletRequest; 27 import javax.servlet.http.HttpServletResponse; 28 import javax.servlet.jsp.JspException; 29 30 import org.apache.struts2.components.Anchor; 31 import org.apache.struts2.components.Component; 32 33 import com.kdyzm.utils.ValidateUtils; 34 import com.opensymphony.xwork2.util.ValueStack; 35 36 /** 37 * @see Anchor 38 */ 39 public class AnchorTag extends AbstractClosingTag { 40 41 private static final long serialVersionUID = -1034616578492431113L; 42 43 protected String href; 44 protected String includeParams; 45 protected String scheme; 46 protected String action; 47 protected String namespace; 48 protected String method; 49 protected String encode; 50 protected String includeContext; 51 protected String escapeAmp; 52 protected String portletMode; 53 protected String windowState; 54 protected String portletUrlType; 55 protected String anchor; 56 protected String forceAddSchemeHostAndPort; 57 58 public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { 59 return new Anchor(stack, req, res); 60 } 61 62 protected void populateParams() { 63 super.populateParams(); 64 65 Anchor tag = (Anchor) component; 66 tag.setHref(href); 67 tag.setIncludeParams(includeParams); 68 tag.setScheme(scheme); 69 tag.setValue(value); 70 tag.setMethod(method); 71 tag.setNamespace(namespace); 72 tag.setAction(action); 73 tag.setPortletMode(portletMode); 74 tag.setPortletUrlType(portletUrlType); 75 tag.setWindowState(windowState); 76 tag.setAnchor(anchor); 77 78 if (encode != null) { 79 tag.setEncode(Boolean.valueOf(encode).booleanValue()); 80 } 81 if (includeContext != null) { 82 tag.setIncludeContext(Boolean.valueOf(includeContext).booleanValue()); 83 } 84 if (escapeAmp != null) { 85 tag.setEscapeAmp(Boolean.valueOf(escapeAmp).booleanValue()); 86 } 87 if (forceAddSchemeHostAndPort != null) { 88 tag.setForceAddSchemeHostAndPort(Boolean.valueOf(forceAddSchemeHostAndPort).booleanValue()); 89 } 90 } 91 92 public void setHref(String href) { 93 this.href = href; 94 } 95 96 public void setEncode(String encode) { 97 this.encode = encode; 98 } 99 100 public void setIncludeContext(String includeContext) { 101 this.includeContext = includeContext; 102 } 103 104 public void setEscapeAmp(String escapeAmp) { 105 this.escapeAmp = escapeAmp; 106 } 107 108 public void setIncludeParams(String name) { 109 includeParams = name; 110 } 111 112 public void setAction(String action) { 113 this.action = action; 114 } 115 116 public void setNamespace(String namespace) { 117 this.namespace = namespace; 118 } 119 120 public void setMethod(String method) { 121 this.method = method; 122 } 123 124 public void setScheme(String scheme) { 125 this.scheme = scheme; 126 } 127 128 public void setValue(String value) { 129 this.value = value; 130 } 131 132 public void setPortletMode(String portletMode) { 133 this.portletMode = portletMode; 134 } 135 136 public void setPortletUrlType(String portletUrlType) { 137 this.portletUrlType = portletUrlType; 138 } 139 140 public void setWindowState(String windowState) { 141 this.windowState = windowState; 142 } 143 144 public void setAnchor(String anchor) { 145 this.anchor = anchor; 146 } 147 148 public void setForceAddSchemeHostAndPort(String forceAddSchemeHostAndPort) { 149 this.forceAddSchemeHostAndPort = forceAddSchemeHostAndPort; 150 } 151 //a標籤只須要重寫一個方法就行 152 @Override 153 public int doEndTag() throws JspException { 154 if(namespace==null||"/".equals(namespace)){ 155 namespace=""; 156 } 157 if(action==null){ 158 action=""; 159 }else{ 160 if(action.endsWith(".action")){ 161 action=action.substring(0, action.indexOf(".")); 162 } 163 } 164 boolean result=ValidateUtils.hasRight(namespace, action, (HttpServletRequest)pageContext.getRequest(), null); 165 // System.out.println("即將訪問"+namespace+action); 166 if(result==true){ 167 // System.out.println("有權限,即將放行!"); 168 return super.doEndTag(); 169 }else{ 170 // System.out.println("沒有權限,即將跳過標籤體!"); 171 return SKIP_BODY; 172 } 173 } 174 }
重寫該標籤類比較複雜,須要同時重寫doStartTag方法和doEndTag方法,並且因爲Action和Namespace的聲明是在Form標籤中,因此還須要遞歸找父節點一直找到Form標籤才行。
核心方法:
1 //Submit標籤須要重寫兩個方法才行 2 @Override 3 public int doStartTag() throws JspException { 4 boolean result=ValidateUtils.hasRight(getFormNamespace(), getFormActionName(), (HttpServletRequest)pageContext.getRequest(), null); 5 if(result==false){ 6 return SKIP_BODY; 7 }else{ 8 return super.doStartTag(); 9 } 10 } 11 @Override 12 public int doEndTag() throws JspException { 13 // System.out.println("表單標籤:"+getFormNamespace()+getFormActionName()); 14 boolean result=ValidateUtils.hasRight(getFormNamespace(), getFormActionName(), (HttpServletRequest)pageContext.getRequest(), null); 15 if(result==false){ 16 return SKIP_BODY; 17 }else{ 18 return super.doEndTag(); 19 } 20 } 21 public String getFormNamespace(){ 22 Tag tag=this.getParent(); 23 while(tag!=null){ 24 if(tag instanceof FormTag){ 25 FormTag formTag=(FormTag) tag; 26 String namespace=formTag.namespace; 27 if(namespace==null||"/".equals(namespace)){ 28 namespace=""; 29 } 30 return namespace; 31 }else{ 32 tag=tag.getParent(); 33 } 34 } 35 return ""; 36 } 37 public String getFormActionName(){ 38 Tag tag=this.getParent(); 39 while(tag!=null){ 40 if(tag instanceof FormTag){ 41 FormTag formTag=(FormTag) tag; 42 String actionName=formTag.action; 43 if(actionName!=null&&actionName.endsWith(".action")){ 44 actionName=actionName.substring(0, actionName.indexOf(".")); 45 return actionName; 46 }else{ 47 actionName=""; 48 return actionName; 49 } 50 }else{ 51 tag=tag.getParent(); 52 } 53 } 54 return ""; 55 }
完整代碼:
1 /* 2 * $Id: SubmitTag.java 681101 2008-07-30 16:06:15Z musachy $ 3 * 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 * 21 * 該類對應着<s:submit>標籤,重寫該類實現對權限細粒度的劃分 22 */ 23 24 package org.apache.struts2.views.jsp.ui; 25 26 import javax.servlet.http.HttpServletRequest; 27 import javax.servlet.http.HttpServletResponse; 28 import javax.servlet.jsp.JspException; 29 import javax.servlet.jsp.tagext.Tag; 30 31 import org.apache.struts2.components.Component; 32 import org.apache.struts2.components.Submit; 33 34 import com.kdyzm.utils.ValidateUtils; 35 import com.opensymphony.xwork2.util.ValueStack; 36 37 /** 38 * @see Submit 39 */ 40 public class SubmitTag extends AbstractClosingTag { 41 42 private static final long serialVersionUID = 2179281109958301343L; 43 44 protected String action; 45 protected String method; 46 protected String align; 47 protected String type; 48 protected String src; 49 50 public Component getBean(ValueStack stack, HttpServletRequest req, HttpServletResponse res) { 51 return new Submit(stack, req, res); 52 } 53 54 protected void populateParams() { 55 super.populateParams(); 56 57 Submit submit = ((Submit) component); 58 submit.setAction(action); 59 submit.setMethod(method); 60 submit.setAlign(align); 61 submit.setType(type); 62 submit.setSrc(src); 63 } 64 65 public void setAction(String action) { 66 this.action = action; 67 } 68 69 public void setMethod(String method) { 70 this.method = method; 71 } 72 73 public void setAlign(String align) { 74 this.align = align; 75 } 76 77 public String getType() { 78 return type; 79 } 80 81 public void setType(String type) { 82 this.type = type; 83 } 84 85 public void setSrc(String src) { 86 this.src = src; 87 } 88 //Submit標籤須要重寫兩個方法才行 89 @Override 90 public int doStartTag() throws JspException { 91 boolean result=ValidateUtils.hasRight(getFormNamespace(), getFormActionName(), (HttpServletRequest)pageContext.getRequest(), null); 92 if(result==false){ 93 return SKIP_BODY; 94 }else{ 95 return super.doStartTag(); 96 } 97 } 98 @Override 99 public int doEndTag() throws JspException { 100 // System.out.println("表單標籤:"+getFormNamespace()+getFormActionName()); 101 boolean result=ValidateUtils.hasRight(getFormNamespace(), getFormActionName(), (HttpServletRequest)pageContext.getRequest(), null); 102 if(result==false){ 103 return SKIP_BODY; 104 }else{ 105 return super.doEndTag(); 106 } 107 } 108 public String getFormNamespace(){ 109 Tag tag=this.getParent(); 110 while(tag!=null){ 111 if(tag instanceof FormTag){ 112 FormTag formTag=(FormTag) tag; 113 String namespace=formTag.namespace; 114 if(namespace==null||"/".equals(namespace)){ 115 namespace=""; 116 } 117 return namespace; 118 }else{ 119 tag=tag.getParent(); 120 } 121 } 122 return ""; 123 } 124 public String getFormActionName(){ 125 Tag tag=this.getParent(); 126 while(tag!=null){ 127 if(tag instanceof FormTag){ 128 FormTag formTag=(FormTag) tag; 129 String actionName=formTag.action; 130 if(actionName!=null&&actionName.endsWith(".action")){ 131 actionName=actionName.substring(0, actionName.indexOf(".")); 132 return actionName; 133 }else{ 134 actionName=""; 135 return actionName; 136 } 137 }else{ 138 tag=tag.getParent(); 139 } 140 } 141 return ""; 142 } 143 }