Day05系統權限

  1. 主頁

Target="right" 而這個right對應name="right"的地方.這樣一來,在第二行左邊點擊超連接,就能夠在右邊顯示頁面了.javascript

 

先將靜態頁面的五個頁面(主頁,上下左右頁面)複製到新建在jsp中的homeAction文件夾中,css

本來的靜態頁面都是html,要將html改成jsp:將jsp的約束代碼和導入公共頁面的代碼複製進html中,再將後綴名都改成.jsphtml

新建一個HomeAction:java

@Controller程序員

public class HomeAction extends ActionSupport{web

    public String index() throws Exception {spring

        return "index";數據庫

    }apache

    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,光標會跳到當前行的行末.

相關文章
相關標籤/搜索