一、角色管理
单击导航栏上的"角色管理"超链接,跳转到角色管理界面,在该界面上显示所有角色,并提供角色的增加和删除、修改超链接。
1.增加新角色(角色授权)
流程:单击增加新角色超链接->Action查询出所有的权限保存到值栈并转到添加新角色页面->填写表单并提交->Action保存表单->重定向到角色管理Action
技术点:表单提交的权限列表时一个整型数组,需要在Action中进行接收并调用相关方法转换成Rright列表;使用到了一些JQuery技术实现更友好的前端交互。
JQuery代码:
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>
2.角色修改和添加使用的方法是同一个方法,略
3.角色删除略。
二、用户授权
形式和流程和角色授权完全一致,略。
三、权限的粗粒度控制
所谓的权限的粗粒度控制指的是改造登陆拦截器使其成为权限控制拦截器,当用户访问某个资源的时候将会根据不同的访问地址判断是否有权限访问,如果有权限访问则放行,否则跳转到错误提示页。
权限控制拦截器中判断权限的流程之前说过了,如下图所示:
1.实现权限控制判断的代码封装到了工具类ValidateUtils的hasRight方法中:
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对象。
2.拦截器代码调用工具方法进行判断
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 }
3.配置struts2的global-result
<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>
四、将所有权限放到application作用域
在权限控制的过程中会经常需要查询权限,如果每次都查询数据库中会对数据库造成很大的负担,最好的方式是将其放到内存,而且使用Map的数据结构更加方便的查询。
将权限集合拿到内存的时机就是tomcat启动完成之前,这里借助spring容器的监听器实现该功能。
实现的技术要点:
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>
五、权限的细粒度控制
1.什么是细粒度控制
所谓细粒度控制就是和粗粒度控制相比较而言的,粗粒度控制旨在当用户访问了无权限访问的资源的时候,拦截其访问;细粒度控制旨在更深一步细化权限控制,不让用户有机会访问无权限访问的资源,也就是说控制关键标签的显示,比如超链接、提交按钮等。
2.实现细粒度控制的方法
方法就是重写struts2的标签类,覆盖掉struts2提供的class文件,这种方式在tomcat下是没有问题的,在其它环境下没有测试,结果未知,最好的方法就是将jar包中对应的class文件剔除,这样类就唯一了。
3注意事项
一定使用struts2完全匹配版本的源代码,否则版本不同特别是差异比较大的,非常有可能会出现意料之外的异常。
4.重写的两个类
org.apache.struts2.views.jsp.ui.AnchorTag 对应着<s:a></s:a>
org.apache.struts2.views.jsp.ui.SubmitTag 对应着<s:submit></s:submit>
(1)重写AnchorTag标签类
重写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 /*org.apache.struts2.views.jsp.ui.AnchorTag.java
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 }
(2)重写SubmitTag标签类
重写该标签类比较复杂,需要同时重写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 /*org.apache.struts2.views.jsp.ui.SubmitTag.java
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 }