Day05系统权限

Posted beihai2018

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day05系统权限相关的知识,希望对你有一定的参考价值。

  1. 主页

技术分享图片

技术分享图片

技术分享图片

Target="right" 而这个right对应name="right"的地方.这样一来,在第二行左边点击超链接,就可以在右边显示页面了.

 

先将静态页面的五个页面(主页,上下左右页面)复制到新建在jsp中的homeAction文件夹中,

技术分享图片

原本的静态页面都是html,要将html改为jsp:将jsp的约束代码和导入公共页面的代码复制进html中,再将后缀名都改为.jsp

技术分享图片

新建一个HomeAction:

@Controller

public class HomeAction extends ActionSupport{

    public String index() throws Exception {

        return "index";

    }

    public String top() throws Exception {

        return "top";

    }

    public String bottom() throws Exception {

        return "bottom";

    }    

    public String left() throws Exception {

        return "left";

    }    

    public String right() throws Exception {

        return "right";

    }

Tips:这个action只做跳转的作用,所以只需要继承ActionSupport.

开多例@Scope("prototype")是为了线程安全,而在这个action中没有公共的资源,所以不存在线程安全问题,也就不用开多例了.

Struts.sml:

    <!-- 首页 -->

    <action name="home_*" class="homeAction" method="{1}">

        <result name="{1}">/WEB-INF/jsp/homeAction/{1}.jsp</result>

    </action>

Tips:因为action中每个方法的返回值都和方法同名,所以可以这么写.

 

一般用户访问的是放在外面的index.jsp,然后再由这个页面跳转到被深藏起来的index.jsp

所以放在外面的index.jsp只有跳转的作用.

技术分享图片

(放在外面的)Index.jsp:

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>

<%

    response.sendRedirect(request.getContextPath() + "/home_index.do");

%>

 

(放在homeAction中的)Index.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

<html>

<head>

<title>ItcastOA</title>

<%@ include file="/WEB-INF/jsp/public/header.jspf" %>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

</head>

 

<frameset rows="100,*,25" framespacing="0" border="0" frameborder="0">

<frame src="home_top.do" name="TopMenu" scrolling="no" noresize />

<frameset cols="180,*" id="resize">

<frame noresize name="menu" src="home_left.do" scrolling="yes" />

<frame noresize name="right" src="home_right.do" scrolling="yes" />

</frameset>

<frame noresize name="status_bar" scrolling="no" src="home_bottom.do" />

</frameset>

 

<noframes>

<body>

</body>

</noframes>

</html>

这里的链接,如home_top.do,还要经过action,再跳到相应jsp页面.

修改下top.jsp:

        <div id="Head1Right">

            <div id="Head1Right_UserName">

<img border="0" width="13" height="14" src="style/images/top/user.gif" /> 您好,<b>${user.name }</b>

            </div>

<div id="Head1Right_SystemButton">

<s:a target="_parent" action="loginout_logout">

                <img width="78" height="20" alt="退出系统" src="style/blue/images/top/logout.gif" />

            </s:a>

</div>

  • target="_parent"

这行代码在这里的作用是:当退出系统时,注销页面会占用整个页面,而不是只在top(第一行)显示!!!

  1. 显示左侧菜单

显示左侧菜单(一)

技术分享图片

左侧菜单是二级菜单,权限数据不会被改变,所以可以采用两重遍历的方式来实现左侧菜单.

Left.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<html>

<head>

<title>导航菜单</title>

<%@ include file="/WEB-INF/jsp/public/header.jspf"%>

<link type="text/css" rel="stylesheet" href="style/blue/menu.css" />

</head>

<body style="margin: 0">

    <div id="Menu">

        <ul id="MenuUl">

            <!-- 第一级权限 -->

            <s:iterator value="topPrivilegeList">

                <li class="level1">

                    <div onClick="menuClick(this)" class="level1Style">

                        <img src="style/images/MenuIcon/FUNC20001.gif" class="Icon" />${name }

                    </div>

                    <ul style="display: none;" class="MenuLevel2">

                        <!-- 第二级权限 -->

                        <s:iterator value="children">

                            <li class="level2">

                                <div class="level2Style">

                                    <img src="style/images/MenuIcon/menu_arrow_single.gif" /> ${name }

                                </div>

                            </li>

                        </s:iterator>

                    </ul>

            </s:iterator>

        </ul>

    </div>

</body>

</html>

  • 因为权限数据一旦被初始化就不能被改变了,所以每次查询的顶级权限集合topPrivilegeList都是一样的.可以采用这样的方式:查一次topPrivilegeList,将其放入最大作用域:application作用域中(如果是放入session作用域中,那只有登陆用户能用,而放到application作用域中的话,所以用户都能用),再利用监听器Listener,(效果是)启动应用程序时,在所有请求到来之前就将topPrivilegeList装好.

    在util包中新建一个OAInitListener:

    @Component

    public class OAInitListener implements ServletContextListener {

        

        private Log log =LogFactory.getLog(OAInitListener.class);

        

        @Resource

        private PrivilegeService privilegeService;

        

        //初始化

        public void contextInitialized(ServletContextEvent sec) {

            

            List<Privilege> topPrivilegeList = privilegeService.findtopPrivilegeList();

            ActionContext.getContext().put("topPrivilegeList", topPrivilegeList);

            log.info("======= topPrivilegeList已经放到application作用域中了! =======");

            

        }

        

        //销毁

        public void contextDestroyed(ServletContextEvent sec) {

        }

     

    创建监听器之后,还得在web.xml中配置:

    <!-- 配置Spring的用于初始化ApplicationContext对象的监听器 -->

    <listener>

        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>

    </listener>

    <context-param>

        <param-name>contextConfigLocation</param-name>

        <param-value>classpath:applicationContext*.xml</param-value>

    </context-param>

    

    <!-- 配置自己的用于初始化的监听器,一定要配置到springContextLoaderListener后面,因为要用到spring容器对象 (先写的先配置) -->

    <listener>

        <listener-class>cn.itcast.oa.util.OAInitListener</listener-class>

    </listener>

 

    <!-- 配置SpringOpenSessionInViewFilter以解决懒加载异常的问题 -->

    <filter>

        <filter-name>OpenSessionInViewFilter</filter-name>

        <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>

    </filter>

    <filter-mapping>

        <filter-name>OpenSessionInViewFilter</filter-name>

        <url-pattern>*.do</url-pattern>

    </filter-mapping>

 

    <!-- 配置Struts2的核心的过滤器 -->

    <filter>

        <filter-name>struts2</filter-name>

        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>

    </filter>

    <filter-mapping>

        <filter-name>struts2</filter-name>

        <url-pattern>/*</url-pattern>

    </filter-mapping>

虽然把OAInitListener放到了spring容器中,也注入了service,但是web.xml和spring没有关联,其中配置监听器的代码不是从容器中拿对象,而是用类全名通过反射的方式创建实例对象,所以OAInitListener根本拿不到service.

正确的OAInitListener:

public class OAInitListener implements ServletContextListener {

    

    private Log log =LogFactory.getLog(OAInitListener.class);

    

    //初始化

    public void contextInitialized(ServletContextEvent sce) {

        ServletContext application = sce.getServletContext();

        

        //spring的容器中取出privilegeService的对象实例

        WebApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(application);

        PrivilegeService privilegeService = (PrivilegeService) ac.getBean("privilegeServiceImpl");

        

        //查询所有顶级的权限列表,并放到application作用域中

        List<Privilege> topPrivilegeList = privilegeService.findtopPrivilegeList();

        application.setAttribute("topPrivilegeList", topPrivilegeList);

        log.info("======= topPrivilegeList已经放到application作用域中了! =======");

        

    }

    

    //销毁

    public void contextDestroyed(ServletContextEvent sce) {

    }

}

  • Tips:不能自己new一个service对象,不然spring不会帮忙管理,一定要用spring已经创建好的对象.而spring会将所有创建好的对象都放到application作用域中,并且提供了一个类WebApplicationContextUtils方便我们取出实例对象.

因为在left.jsp中是这样获取topPrivilegeList的: <s:iterator value="topPrivilegeList">.

这是ognl表达式,这么写并拿不到存在application作用域中的topPrivilegeList.应该改为

Left.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<html>

<head>

<title>导航菜单</title>

<%@ include file="/WEB-INF/jsp/public/header.jspf"%>

<link type="text/css" rel="stylesheet" href="style/blue/menu.css" />

</head>

<body style="margin: 0">

    <div id="Menu">

        <ul id="MenuUl">

            <!--一级菜单 -->

            <s:iterator value="#application.topPrivilegeList">

                <li class="level1">

                    <div onClick="menuClick(this)" class="level1Style">

                        <img src="style/images/MenuIcon/FUNC20001.gif" class="Icon" />${name }

                    </div>

                    <ul style="display: none;" class="MenuLevel2">

                        <!-- 二级菜单 -->

                        <s:iterator value="children">

                            <li class="level2">

                                <div class="level2Style">

                                    <img src="style/images/MenuIcon/menu_arrow_single.gif" /> ${name }

                                </div>

                            </li>

                        </s:iterator>

                    </ul>

            </s:iterator>

        </ul>

    </div>

</body>

</html>

但是改成这样之后却会报懒加载异常:

技术分享图片技术分享图片

懒加载:对象被加载的时候,其属性不会被加载,只有在真正需要这些资源的时候才会加载。其思想简单来说就是拖到最后一刻,直到万不得已才加载,才开始占用资源。

为什么在web.xml中加上了解决懒加载问题的代码,却还是会出现懒加载问题?

    <!-- 配置SpringOpenSessionInViewFilter以解决懒加载异常的问题 -->

    <filter>

        <filter-name>OpenSessionInViewFilter</filter-name>

        <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>

    </filter>

    <filter-mapping>

        <filter-name>OpenSessionInViewFilter</filter-name>

        <url-pattern>*.do</url-pattern>

    </filter-mapping>

因为web.xml中的这段代码解决的只是同一个请求的懒加载问题,当有多个请求出现懒加载问题时,它是解决不了的.

详解:

技术分享图片

  • 在同一个请求中,service开启了事务,打开session,拿到对象,却在提交事务,关闭session之后才用到对象的懒加载属性,这样就会出现懒加载异常.所以得延迟关闭session.而web.xml中的那段代码正是为了延迟关闭session.
  • 当有多个请求时,比如:
  • 现在有两个请求(每个请求都有自己的事务和session),第一个请求拿到对象之后,没用到对象的懒加载属性就关闭了,该对象被存到某个地方.此时第二个请求直接使用对象的懒加载属性,就会出现懒加载异常的问题.
  • 因为被加载的对象之和第一个请求有关联,和第二个请求无关.

在此正是多个请求导致的懒加载异常,启动应用程序时topPrivilegeList就被加载了(Listener的特性),过两个小时之后再做第一次访问(第一个请求?),左侧菜单为一个请求,这时就要用到topPrivilegeList的懒加载属性(菜单中的二级菜单:children属性),而他们根本不是同一个请求,所以会出现懒加载异常.解决方案:直接关掉懒加载.

Privilege.hbm.xml:

<!-- children属性,表达的是本对象与Privilege(children)的一对多关系 -->

<set name="children" order-by="id ASC" lazy="false">

    <key column="parentId"></key>

    <one-to-many class="Privilege"/>

</set>

同理,登录时(第一个请求),会将user放到session中,而加载user对象时却没有加载user下的roles(懒加载),所以当要用到user下的所有roles时(第二个请求),又会出现懒加载问题.Roles下的privileges也一样,所以都要改.

User.hbm.xml:

<!-- roles属性,表达的是本对象与Role的多对多关系 -->

<set name="roles" table="itcast_user_role" lazy="false">

    <key column="userId"></key>

    <many-to-many class="Role" column="roleId"></many-to-many>

</set>

Role.hbm.xml:

<!-- privileges属性,表达的是本对象与Privilege的多对多关系 -->

<set name="privileges" table="itcast_role_privilege" lazy="false">

    <key column="roleId"></key>

    <many-to-many class="Privilege" column="privilegeId"></many-to-many>

</set>

修改完成之后的效果图:

技术分享图片

因为left.jsp中遍历二级菜单时有这样一行代码style="display: none;",这就导致左侧菜单默认不显示二级菜单,将这行代码删去之后,就会默认显示所有菜单.

1.给左侧菜单添加超链接.因为url已经被存进数据库中

技术分享图片

所以可以这么写:

<a href="${pageContext.request.contextPath }${url}.do" target="right">${name }</a>

2.给一级菜单更换图片:

可以把要用到的图片复制一份,将副本名字改成数据库中权限(即菜单)的id值:

技术分享图片

然后导入图片的代码改成这样:

<img src="style/images/MenuIcon/${id }.gif" class="Icon" />

3.当点击一级菜单时,会显示他所有的二级菜单,再点击一下就会关闭他所有的二级菜单:

onClick=" $(this).next().toggle() "

此时left.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<html>

<head>

<title>导航菜单</title>

<%@ include file="/WEB-INF/jsp/public/header.jspf"%>

<link type="text/css" rel="stylesheet" href="style/blue/menu.css" />

</head>

<body style="margin: 0">

    <div id="Menu">

        <ul id="MenuUl">

            <!-- 一级菜单 -->

            <s:iterator value="#application.topPrivilegeList">

                <li class="level1">

                    <div onClick=" $(this).next().toggle() " class="level1Style">

                        <img src="style/images/MenuIcon/${id }.gif" class="Icon" />

                        ${name }

                    </div>

                    <ul class="MenuLevel2">

                        <!-- 二级菜单 -->

                        <s:iterator value="children">

                            <li class="level2">

                                <div class="level2Style">

                                    <img src="style/images/MenuIcon/menu_arrow_single.gif" />

                                    <a href="${pageContext.request.contextPath }${url}.do" target="right">${name }</a>

                                </div>

                            </li>

                        </s:iterator>

                    </ul>

            </s:iterator>

        </ul>

    </div>

</body>

</html>

Tips: target="right" 将页面指定在name="right"的地方显示(即右侧)

技术分享图片

 

显示左侧菜单(二):显示有权限的菜单

思路:可以在left.jsp遍历topPrivilegeList的同时,取出当前存在session中的user(正在登录的用户),判断user是否有当前正在遍历的权限,有则显示权限,没有则进行下一次遍历,直到将topPrivilegeList遍历完成.

Left.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"

    pageEncoding="UTF-8"%>

<html>

<head>

<title>导航菜单</title>

<%@ include file="/WEB-INF/jsp/public/header.jspf"%>

<link type="text/css" rel="stylesheet" href="style/blue/menu.css" />

</head>

<body style="margin: 0">

    <div id="Menu">

        <ul id="MenuUl">

            <!-- 一级菜 -->

            <s:iterator value="#application.topPrivilegeList">

                <s:if test=" #session.user.hasPrivilegeByName(name) ">

                    <li class="level1">

                        <div onClick=" $(this).next().toggle() " class="level1Style">

                            <img src="style/images/MenuIcon/${id }.gif" class="Icon" />

                            ${name }

                        </div>

                        <ul class="MenuLevel2">

                            <!-- 二级菜单 -->

                            <s:iterator value="children">

                                <s:if test=" #session.user.hasPrivilegeByName(name) ">

                                    <li class="level2">

                                        <div class="level2Style">

                                            <img src="style/images/MenuIcon/menu_arrow_single.gif" />

                                            <a href="${pageContext.request.contextPath }${url}.do" target="right">${name }</a>

                                        </div>

                                    </li>

                                </s:if>

                            </s:iterator>

                        </ul>

                    </li>

                </s:if>

            </s:iterator>

        </ul>

    </div>

</body>

</html>

Tips:<s:if test=" #session.user.hasPrivilegeByName(name) ">

hasPrivilegeByName()是user中的方法,参数name是当前正在遍历的权限的名字.

Ognl表达式中可以调用方法(而el表达式不能).

User.java中得新增方法:

    /**

     * 判断是否有权限

     * @param priviName 权限的名称

     */

    public boolean hasPrivilegeByName(String privName){

        for (Role role : roles) {

            for (Privilege p : role.getPrivileges()) {

                if(p.getName().equals(privName)){

                    return true;

                }

            }

        }

        return false;

    }

然而,测试运行却会报错,因为当前没有登录用户.

技术分享图片

这个target是指拥有当前方法的对象实例,在此为user.

当登录admin后,却什么也看不到.

技术分享图片

这是因为admin是特殊用户,没有与任何岗位关联.

所以这时还得继续判断是否为admin.

User.java:

    /**

     * 判断是否有权限

     * @param priviName 权限的名称

     */

    public boolean hasPrivilegeByName(String privName){

        //如果是超级管理员,就有所有权限

        if(isAdmin()){

            return true;

        }

        

        for (Role role : roles) {

            for (Privilege p : role.getPrivileges()) {

                if(p.getName().equals(privName)){

                    return true;

                }

            }

        }

        return false;

    }

    

    /*

     * 判断当前用户是否为admin

     */

    public boolean isAdmin(){

        return "admin".equals(loginName);

    }

Tips:用equals()进行比较时,一般把一定不为null的值放在左边.因为:(不为nul)l.equals(任何值(包括null))其返回值都不为空,此时就算右边为空,他也会返回false.而如果(null).equals(任何值(包括不为null的值))会抛空指针异常.

三.显示右侧的链接--思路:改jar包中源码的方式

打个比方,如果程序员没有删除用户的权限,那么身为程序员的用户在用户列表页面,要么看到的"用户删除"链接是灰色的,要么根本看不到"用户删除"链接.在此用的是第二种方式.有一下几种方案:

技术分享图片

方案1太麻烦,不是用struts标签,而是用其他标签(如原始标签:<a/>)时适合用方案三,在此我们选择用方案四.

首先要找到<s:a/>所在源码:随便找到个<s:a/>标签,按住ctrl,左键单击,就进入了这里:

技术分享图片

由此得知该标签所在类为:

<tag-class>org.apache.struts2.views.jsp.ui.AnchorTag</tag-class>

复制类名,按住CTRL + SHIFT + T :跳转到该类.但是该类是只读形式.

修改方法:新建一个包,包名和该类所在包的包名相同,创建一个类,类名和该类一样,全选复制该类内容,粘贴过来,再修改相应方法.

原理:1.虽然这两个类包名和类名都一样,但他们在不同jar包里,所有这是可行的.2.启动应用程序的时候,所有jar包都会被加载然后被放到缓存中,之后就不会再被加载了,重点是,哪个类先被加载,哪个类就会被使用.项目中,它都是先加载我们自己的类,找不到的话才会去加载lib中的类.

用继承的方式不太好,因为万一该类是final修饰的,那就不可继承,又或者我们要修改的方法是private修饰的,那也不能继承.

找该类所在包名的方法:

技术分享图片

具体修改:

AnchorTag.java:

@Override

public int doEndTag() throws JspException {

    //获取当前登录的用户

    User user = (User) pageContext.getSession().getAttribute("user");

    if(user == null){

        throw new RuntimeException("当前没有登陆用户!");

    }

    

    //获取所需要的权限url(action属性值中,但需要处理一下)

    String privUrl = "/" + action;

    

    //a.去掉后面的参数字符串(如果有)

    int pro = privUrl.indexOf("?");

    if(pro > -1){

        privUrl = privUrl.substring(0, pro);

    }

    //b.去掉后面的UI后缀(如果有)

    if(privUrl.endsWith("UI")){

        privUrl = privUrl.substring(0, privUrl.length()-2);

    }

    

    //根据权限决定是否显示超链接

    if(user.hasPrivilegeByUrl(privUrl)){

        return super.doEndTag();    //输出<a/>标签,并继续执行此标签后面的jsp代码

    }else{

        return BodyTagSupport.EVAL_PAGE;    //不输出<a/>,并继续执行此标签后面的jsp代码

    }

}

  • Tips: 1.doEndTag()是被继承过来的方法,所有需要重载;

2. BodyTagSupport.EVAL_PAGE;    //不输出<a/>,并继续执行此标签后面的jsp代码.如果没有权限,就不输出<a/>,也就看不到超链接了;

3.在这个类中,Struts框架对action进行了定义,页面上的超链接地址

技术分享图片

就是保存到了action中,所以在这里可以直接String privUrl = "/" + action;"/"是因为数据库中的url地址

4.不确定privUrl的截取是否正确的话,可以在 if(pro > -1){ 这行打断点.首先登录用户,然后它会自动跳到如下界面:

技术分享图片

点击一下页面上的用户管理(即让程序进入对privUrl的判断),对String privUrl = "/" + action;中的privUrl右键单击,选择"Watch",会出现如下窗口:

技术分享图片

然后多次单击

技术分享图片

进入下一行代码,多次观察Expressions窗口中的privUrl的值是否正确.此时再点击

技术分享图片

进入下一次判断,再点击…进入下一行代码,观察…如此反复操作.

 

技术分享图片

前面都加了"/";

此时Uer.java再新增一个方法(通过url来判断是否有权限):

/**

* 判断是否有权限

* @param priviUrl 权限的url

*/

public boolean hasPrivilegeByUrl(String privUrl) {

    //如果是超级管理员,就有所有的权限

    if(isAdmin()){

        return true;

    }

    //如果是一般用户,就得判断是否有权限

    for (Role role : roles) {

        for (Privilege p : role.getPrivileges()) {

            if(privUrl.equals(p.getUrl())){

                return true;

            }

        }

    }

    return false;

}

Tips: privUrl.equals(p.getUrl())privUrl放在前面是因为p.getUrl有可能为null.若是顶级菜单就没有url.

技术分享图片

以后还可能需要通过判断url来判断是否有权限,那么就可以将AnchorTag.java中对url的判断语句放到user.java里,修改后

AnchorTag.java:

@Override

    public int doEndTag() throws JspException {

    //获取当前登录的用户

    User user = (User) pageContext.getSession().getAttribute("user");

    if(user == null){

        throw new RuntimeException("当前没有登陆用户!");

    }

      

    //获取所需要的权限url(action属性值中,但需要处理一下)

    String privUrl = "/" + action;

      

    //根据权限决定是否显示超链接

    if(user.hasPrivilegeByUrl(privUrl)){

        return super.doEndTag();    //输出<a/>标签,并继续执行此标签后面的jsp代码

    }else{

        return BodyTagSupport.EVAL_PAGE;    //不输出<a/>,并继续执行此标签后面的jsp代码

    }

    }

 

User.java:

    /**

     * 判断是否有权限

     * @param priviUrl 权限的url

     */

    public boolean hasPrivilegeByUrl(String privUrl) {

        //如果是超级管理员,就有所有的权限

        if(isAdmin()){

            return true;

        }

        

        //a.去掉后面的参数字符串(如果有)

     int pro = privUrl.indexOf("?");

    if(pro > -1){

        privUrl = privUrl.substring(0, pro);

    }

    //b.去掉后面的UI后缀(如果有)

    if(privUrl.endsWith("UI")){

     privUrl = privUrl.substring(0, privUrl.length()-2);

    }

      

        //如果是一般用户,就得判断是否有权限

        for (Role role : roles) {

            for (Privilege p : role.getPrivileges()) {

                if(privUrl.equals(p.getUrl())){

                    return true;

                }

            }

        }

        return false;

    }

    

    /*

     * 判断当前用户是否为admin

     */

    public boolean isAdmin(){

        return "admin".equals(loginName);

    }

 

  1. 拦截验证每一个请求的权限

作用:避免用户在没有登录的情况下,却能通过直接输入url的方式来进行某些操作.比如没有登录时输入http://localhost:8080/ItcastOA/user_list.do来查看用户列表,这是不允许的,必须得先登录,然后再判断用户是否具有进行该操作的权限.

(一)测试拦截器

(这集没声音,光知道操作,理解不深,所以谋得解释啊啊啊啊啊)

技术分享图片

先在util包中创建一个类

技术分享图片

CheckPrivilegeInterceptor:

package cn.itcast.oa.util;

 

import com.opensymphony.xwork2.ActionInvocation;

import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

 

public class CheckPrivilegeInterceptor extends AbstractInterceptor {

 

    @Override

    public String intercept(ActionInvocation invocation) throws Exception {

        System.out.println("======>拦截器(前)<======");

        String result = invocation.invoke(); // 放行

        System.out.println("======>拦截器(后)<======");

        return result;

    }

}

 

接着是到struts.xml中进行配置:

<interceptors>

    <!-- 声明拦截器 -->

    <interceptor name="CheckPrivilege" class="cn.itcast.oa.util.CheckPrivilegeInterceptor"></interceptor>

    <!-- 配置我们自己的拦截器栈 -->

    <interceptor-stack name="myDefaultStack">

        <interceptor-ref name="CheckPrivilege"></interceptor-ref>

        <interceptor-ref name="defaultStack"></interceptor-ref>

    </interceptor-stack>

</interceptors>

<!-- 配置默认的拦截器栈 -->

<default-interceptor-ref name="myDefaultStack"></default-interceptor-ref>

一定要引入默认拦截器.这段配置代码应该是要放到前面的.

(二)

CheckPrivilegeInterceptor:

package cn.itcast.oa.util;

 

import cn.itcast.oa.domain.User;

 

import com.opensymphony.xwork2.ActionContext;

import com.opensymphony.xwork2.ActionInvocation;

import com.opensymphony.xwork2.interceptor.AbstractInterceptor;

 

public class CheckPrivilegeInterceptor extends AbstractInterceptor {

 

    @Override

    public String intercept(ActionInvocation invocation) throws Exception {

        

        //准备数据

        //a.获取当前登录的用户

        User user = (User) ActionContext.getContext().getSession().get("user");

        //b.获取当前访问的url

        String namespace = invocation.getProxy().getNamespace();

        String actionName = invocation.getProxy().getActionName();

        if(null == namespace || "".equals(namespace)){

            namespace = "/";

        }

        if(!namespace.endsWith("/")){

            namespace += "/";

        }

        String url = namespace + actionName;

        

        //一,如果用户未登录,则转到登录页面

        if(user == null){

            //a.如果当前访问的是登录页面:loginout_loginUI,loginout_login,则放行

            if(url.startsWith("/loginout_login")){

                return invocation.invoke();

                

            //b.如果访问的不是登录登录功能,就转到登录页面

            }else{

                return "loginUI";

            }

            

        //二,如果用户已登录,则判断权限

        }else{

            //a.如果有权限访问当前url,则放行

            if(user.hasPrivilegeByUrl(url)){

                return invocation.invoke();

                

            //b.如果没有权限访问当前url,则转到提示消息的页面

            }else{

                return "noPrivilegeUI";

            }

        }

    }

创建noPrivilegeUI.jsp(作用:提示没有权限),并套用静态页面:

技术分享图片

noPrivilegeUI.jsp:

<%@ page language="java" contentType="text/html; charset=UTF-8"%>

<HTML>

<HEAD>

<TITLE>没有权限</TITLE>

<%@ include file="/WEB-INF/jsp/public/header.jspf" %>

<SCRIPT TYPE="text/javascript">

</SCRIPT>

</HEAD>

<BODY>

 

<DIV ID="Title_bar">

<DIV ID="Title_bar_Head">

<DIV ID="Title_Head"></DIV>

<DIV ID="Title"><!--页面标题-->

<IMG BORDER="0" WIDTH="13" HEIGHT="13" SRC="${pageContext.request.contextPath}/style/images/title_arrow.gif"/> 提示

</DIV>

<DIV ID="Title_End"></DIV>

</DIV>

</DIV>

 

 

<!--显示表单内容-->

<DIV ID="MainArea">

        <DIV CLASS="ItemBlock_Title1">

</DIV>

 

<DIV CLASS="ItemBlockBorder" STYLE="margin-left: 15px;">

<DIV CLASS="ItemBlock" STYLE="text-align: center; font-size: 16px;">

出错了,您没有权限访问此功能!

</DIV>

</DIV>

 

<!-- 操作 -->

<DIV ID="InputDetailBar">

<A HREF="javascript:history.go(-1);"><IMG SRC="${pageContext.request.contextPath}/style/images/goBack.png"/></A>

</DIV>

 

</DIV>

 

</BODY>

</HTML>

在struts.xml中配置全局变量(拦截器中的返回值和action中的返回值效果是一样的):

<!-- 全局配置 -->

<global-results>

<result name="loginUI">/WEB-INF/jsp/loginoutAction/loginUI.jsp</result>

<result name="noPrivilegeUI">/noPrivilegeUI.jsp</result>

</global-results>

到此结束的话,系统会存在一个很大的漏洞:因为还有很多登录用户都可以访问的url(也就是用户登录之后,不需要再进行控制的功能(如首页,注销))没被存进数据库中.这就导致用户被系统误判为是没有权限!!

技术分享图片

思路:将所有权限的url查询出来放到一个集合中,如果是超级管理员,则它有所有权限(返回true).如果当前要访问的权限url不在权限url集合中,返回true(代表只要是登录用户都可以访问);如果当前权限url在权限url集合中,则要继续判断当前用户是否具有该权限.

由于权限url是固定的(从初始化权限数据开始就固定下来了),所有可以利用监听器的思想.在启动应用程序,所有请求到来之前,就将url集合放入最大作用域application中(这样就只需要查询一次,以后都不用再查了).

User.java:

/**

* 判断是否有权限

* @param priviUrl 权限的url

*/

public boolean hasPrivilegeByUrl(String privUrl) {

    //如果是超级管理员,就有所有的权限

    if(isAdmin()){

        return true;

    }

    

    //a.去掉后面的参数字符串(如果有)

    int pro = privUrl.indexOf("?");

    if(pro > -1){

        privUrl = privUrl.substring(0, pro);

    }

    //b.去掉后面的UI后缀(如果有)

    if(privUrl.endsWith("UI")){

        privUrl = privUrl.substring(0, privUrl.length()-2);

    }

    

    //如果是一般用户,就得判断是否有权限

    //a.如果这个url是不需要控制的功能(登录后就可以直接使用的,如首页,注销),这时应该直接返回ture

    Collection<String> allPrivilegeUrls = (Collection<String>) ActionContext.getContext().getApplication().get("allPrivilegeUrls");

    if(!allPrivilegeUrls.contains(privUrl)){

        return true;

        

    //b.如果这个url是需要控制的功能(登录还得有对应权限才能使用),这时应该判断权限

    }else{

        for (Role role : roles) {

            for (Privilege p : role.getPrivileges()) {

                if(privUrl.equals(p.getUrl())){

                    return true;

                }

            }

        }    

        return false;

    }

}    

/*

* 判断当前用户是否为admin

*/

public boolean isAdmin(){

    return "admin".equals(loginName);

}

OAInitListener.java:

package cn.itcast.oa.util;

 

import java.util.List;

 

import javax.servlet.ServletContext;

import javax.servlet.ServletContextEvent;

import javax.servlet.ServletContextListener;

 

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

import org.springframework.web.context.WebApplicationContext;

import org.springframework.web.context.support.WebApplicationContextUtils;

 

import cn.itcast.oa.domain.Privilege;

import cn.itcast.oa.service.PrivilegeService;

 

import com.opensymphony.xwork2.ActionContext;

@SuppressWarnings("unused")

public class OAInitListener implements ServletContextListener {

    

    private Log log =LogFactory.getLog(OAInitListener.class);

    

    //初始化

    public void contextInitialized(ServletContextEvent sce) {

        ServletContext application = sce.getServletContext();

        

        //从spring的容器中取出privilegeService的对象实例

        WebApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(application);

        PrivilegeService privilegeService = (PrivilegeService) ac.getBean("privilegeServiceImpl");

        

        //查询所有顶级的权限列表,并放到application作用域中

        List<Privilege> topPrivilegeList = privilegeService.findtopPrivilegeList();

        application.setAttribute("topPrivilegeList", topPrivilegeList);

        log.info("======= topPrivilegeList已经放到application作用域中了! =======");

        

        //查询所有权限列表,并放到application作用域中

        List<String> allPrivilegeUrls = privilegeService.getAllPrivilegeUrls();

        application.setAttribute("allPrivilegeUrls", allPrivilegeUrls);

        log.info("======= allPrivilegeUrls已经放到application作用域中了! =======");

        

        

    }

    

    //销毁

    public void contextDestroyed(ServletContextEvent sce) {

    }

 

}

privilegeServiceImpl:

/**

* 查询所有权限的url

*/

public List<String> getAllPrivilegeUrls() {

return getSession().createQuery(//

        "select distinct p.url from Privilege p where p.url is not null")//

        .list();

}

查出来的url要避免空值和重复的值,因为数据库中就有空值和重复的值:

技术分享图片

 

(三)一些优化

1).用户登录后,在Tomcat重启后,还应是登录状态.

原理: tomcat在(正常)关闭的时候,会把内存中的session序列化到本地硬盘上,再启动服务器时,会把反序列化拿到硬盘中还原session信息.

演示:

(此时服务器开着)正常关闭服务器,会看到session被序列化到了本地硬盘上:

技术分享图片

再启动服务器,session的序列化信息又会被还原到内存中:

技术分享图片

前提:User.java和它所关联的所有实体都实现序列化: implements Serializable.

技术分享图片技术分享图片

 

技术分享图片

 

 

技术分享图片

效果:

打开服务器,以超级管理员的身份登录系统:

技术分享图片

此时右键TomcatàRestart:

技术分享图片

刷新页面,原本系统会跳到登录页面,要求重新登录,可实现序列化后,session中的user还在,所以用户还是保持着登录状态,不需要再重新登录了:

技术分享图片

2)页面嵌套的问题.

演示问题:

  1. 先退出当前用户(有两种方法):

第一种方法是启动服务器,登录用户,右键退出按钮,单击"在新窗口中打开链接"

技术分享图片

在新窗口中刷新一次.(即退出了当前用户)

技术分享图片

第二种方法是直接等待session过期(期间不要进行任何操作),session默认过期时间是半小时.

  1. 当退出用户后,在页面上点击任何一个超链接,系统都会转到登录界面.此时就会出现页面嵌套的情况:

    技术分享图片

登录后也还有页面嵌套的问题:

技术分享图片

此时手动刷新一下,页面就会恢复正常了.

我们要做的就是避免页面嵌套的问题,应在loginUI.jsp中加入自动刷新的代码:

<script type="text/javascript">

        if(window.parent != window){

            window.parent.location.href = window.location.href;

        }

</script>

Window指的就是整个页面(整个大框),所以window是没有上级的,它的parent就是它自己.当window.parent != window,说明出现了页面嵌套的情况,这时页面会自动刷新window.parent.location.href = window.location.href;

 

  • 总结:

1.显示左侧菜单需要在left.jsp中进行两次遍历(第一次是TopPrivilegeList,第二次是children.其中TopPrivilegeList是利用监听器放到最大作用域application中的), 在两次遍历的同时还分别调用了User中的hasPrivilegeByName()来判断是否有权限;

2.显示右侧超链接是用了修改jar源码的方式,还调用了User中的hasPrivilegeByUrl()来判断是否有权限;

3.拦截每一次请求是专门写了一个拦截器,其中也调用了User中的hasPrilegeByUrl()来判断是否有权限.拦截器的思路:用户未登录的时候有两种情况:要是当前要访问的url是登录页面的话,就放行,否则就跳转到登录页面.用户已登陆的时候,也有两种情况:要是没有当前访问的url权限,则提示没有权限,要是有权限就放行.

而因为有很多登录用户都能访问的url没有被存进数据库中(比如首页,注销),所以当用户访问这些url时也会被认为是没有权限而无法访问.这是我们就应该修改User中的hasPrilegeByUrl().hasPrilegeByUrl()的思路是:若是超级管理员则有所有权限(返回true),处理被传递过来的url,对url进行判断.若是当前要访问的url不在所有权限url集合中,则返回true.(这说明所有登录用户都能访问这个url).否则就要进一步判断用户是否有权限了.至于url集合:因为权限url是固定的,所以它也可以利用监听器存到最大作用域application中(好处是查一次就好了,查过之后它会被放入缓存中,以后用的时候就直接从缓存中拿,而不用查第二次了).

  • 小技巧:

技术分享图片

当这个双箭头被选中时,右边打开任何一个文件,在左边都会实时显示该文件所在的位置.如:

技术分享图片

 

快捷键:CTRL+SHIFT+T 查找所有java文件,按回车后会跳到那个页面.比如按CTRL+SHIFT+T后打入UserService,按回车之后就跳到了UserService页面.

按home,光标会跳到当前行的行首,按end,光标会跳到当前行的行末.

以上是关于Day05系统权限的主要内容,如果未能解决你的问题,请参考以下文章

Day05 权限和归属(ADMIN05)

权限管理系统笔记

Java SSM项目 day01 企业权限管理系统(IDEA版)源代码

Java SSM项目 day01 企业权限管理系统(IDEA版)源代码

day09-linux特殊权限及软连接

Day373&374.shiro架构&认证 -Shiro