Shiro第一篇【Shiro的基礎知識、回顧URL攔截】

Shiro基礎知識

在學習Shiro這個框架之前,首先我們要先了解Shiro需要的基礎知識:權限管理

什麼是權限管理?

只要有用戶參與的系統一般都要有權限管理,權限管理實現對用戶訪問系統的控制,按照安全規則或者安全策略控制用戶可以訪問而且只能訪問自己被授權的資源

對權限的管理又分爲兩大類別:

  • 用戶認證
  • 用戶授權

用戶認證

用戶認證,用戶去訪問系統,系統要驗證用戶身份的合法性

最常用的用戶身份驗證的方法:1、用戶名密碼方式、2、指紋打卡機、3、基於證書驗證方法。。系統驗證用戶身份合法,用戶方可訪問系統的資源。

舉個例子:

  • 當我們輸入了自己的淘寶的賬戶和密碼,才能打開購物車

用戶認證的流程:

  • 判斷該資源能否不認證就能訪問【登陸頁面、首頁】
  • 如果該資源需要認證後才能訪問,那麼判斷該訪問者是否認證了
  • 如果還沒有認證,那麼需要返回到【登陸頁面】進行認證
  • 認證通過後才能訪問資源

這裏寫圖片描述

從用戶認證我們可以抽取出這麼幾個概念

  • subject主體:理解爲用戶,可能是程序,都要去訪問系統的資源,系統需要對subject進行身份認證
  • principal身份信息:通常是唯一的,一個主體還有多個身份信息,但是都有一個主身份信息(primary principal)【我們可以選擇身份證認證、學生證認證等等都是我們的身份信息】
  • credential憑證信息:可以是密碼 、證書、指紋。

總結:主體在進行身份認證時需要提供身份信息和憑證信息。

用戶授權

用戶授權,簡單理解爲訪問控制,在用戶認證通過後,系統對用戶訪問資源進行控制,用戶具有資源的訪問權限方可訪問

用戶授權的流程

  • 到達了用戶授權環節,當然是需要用戶認證之後了
  • 用戶訪問資源,系統判斷該用戶是否有權限去操作該資源
  • 如果該用戶有權限才能夠訪問,如果沒有權限就不能訪問了

這裏寫圖片描述

授權的過程可以簡單理解爲:主體認證之後,系統進行訪問控制

subject必須具備資源的訪問權限纔可訪問該資源..

權限/許可(permission) :針對資源的權限或許可,subject具有permission訪問資源,如何訪問/操作需要定義permission,權限比如:用戶添加、用戶修改、商品刪除

資源可以分爲兩種

  • 資源類型:系統的用戶信息就是資源類型,相當於java類。
  • 資源實例:系統中id爲001的用戶就是資源實例,相當於new的java對象。

權限管理模型

一般地,我們可以抽取出這麼幾個模型:

  • 主體(賬號、密碼)
  • 資源(資源名稱、訪問地址)
  • 權限(權限名稱、資源id)
  • 角色(角色名稱)
  • 角色和權限關係(角色id、權限id)
  • 主體和角色關係(主體id、角色id)

這裏寫圖片描述

通常企業開發中將資源和權限表合併爲一張權限表,如下:

  • 資源(資源名稱、訪問地址)
  • 權限(權限名稱、資源id)

合併爲:

  • 權限(權限名稱、資源名稱、資源訪問地址)

這裏寫圖片描述

分配權限

用戶需要分配相應的權限纔可訪問相應的資源。權限是對於資源的操作許可。

通常給用戶分配資源權限需要將權限信息持久化,比如存儲在關係數據庫中。把用戶信息、權限管理、用戶分配的權限信息寫到數據庫(權限數據模型)

基於角色訪問控制

RBAC(role based access control),基於角色的訪問控制。


//如果該user是部門經理則可以訪問if中的代碼
if(user.hasRole('部門經理')){
    //系統資源內容
    //用戶報表查看
}

角色針對人劃分的,人作爲用戶在系統中屬於活動內容,如果該 角色可以訪問的資源出現變更,需要修改你的代碼了


if(user.hasRole('部門經理') || user.hasRole('總經理')  ){
    //系統資源內容
    //用戶報表查看
}

基於角色的訪問控制是不利於系統維護(可擴展性不強)。

基於資源的訪問控制

RBAC(Resource based access control),基於資源的訪問控制。

資源在系統中是不變的,比如資源有:類中的方法,頁面中的按鈕。


對資源的訪問需要具有permission權限,代碼可以寫爲:

if(user.hasPermission ('用戶報表查看(權限標識符)')){
    //系統資源內容
    //用戶報表查看
}

建議使用基於資源的訪問控制實現權限管理


粗粒度和細粒度權限

細粒度權限管理:對資源實例的權限管理。資源實例就資源類型的具體化,比如:用戶id爲001的修改連接,1110班的用戶信息、行政部的員工。細粒度權限管理就是數據級別的權限管理。

粗粒度權限管理比如:超級管理員可以訪問戶添加頁面、用戶信息等全部頁面。部門管理員可以訪問用戶信息頁面包括 頁面中所有按鈕。

粗粒度和細粒度例子


系統有一個用戶列表查詢頁面,對用戶列表查詢分權限,

如果粗顆粒管理,張三和李四都有用戶列表查詢的權限,張三和李四都可以訪問用戶列表查詢。

進一步進行細顆粒管理,張三(行政部)和李四(開發部)只可以查詢自己本部門的用戶信息。

張三隻能查看行政部 的用戶信息,李四隻能查看開發部門的用戶信息。

細粒度權限管理就是數據級別的權限管理。

如何實現粗粒度權限管理?

粗粒度權限管理比較容易將權限管理的代碼抽取出來在系統架構級別統一處理。比如:通過springmvc的攔截器實現授權

對細粒度權限管理在數據級別是沒有共性可言,針對細粒度權限管理就是系統業務邏輯的一部分在業務層去處理相對比較簡單

比如:部門經理只查詢本部門員工信息,在service接口提供一個部門id的參數,controller中根據當前用戶的信息得到該 用戶屬於哪個部門,調用service時將部門id傳入service,實現該用戶只查詢本部門的員工。

基於URL攔截

基於url攔截的方式實現在實際開發中比較常用的一種方式。

對於web系統,通過filter過慮器實現url攔截,也可以springmvc的攔截器實現基於url的攔截。

使用權限管理框架實現

對於粗粒度權限管理,建議使用優秀權限管理框架來實現,節省開發成功,提高開發效率。

shiro就是一個優秀權限管理框架。

回顧URL攔截

我們在學習的路途上也是使用過幾次URL對權限進行攔截的

當時我們做了權限的增刪該查的管理系統,但是在權限表中是沒有把資源添加進去,我們使用的是Map集合來進行替代的
http://blog.csdn.net/hon_3y/article/details/61926175

隨後,我們學習了動態代理和註解,我們也做了一個基於註解的攔截

  • 在Controller得到service對象的時候,service工廠返回的是一個動態代理對象回去
  • Controller拿着代理對象去調用方法,代理對象就會去解析該方法上是否有註解
  • 如果有註解,那麼就需要我們進行判斷該主體是否認證了,如果認證了就判斷該主體是否有權限
  • 當我們解析出該主體的權限和我們註解的權限是一致的時候,才放行!

http://blog.csdn.net/hon_3y/article/details/70767050

流程

這裏寫圖片描述

認證的JavaBean

我們之前認證都是放在默認的Javabean對象上的,現在既然我們準備學Shiro了,我們就得專業一點,弄一個專門存儲認證信息的JavaBean



/**
 * 用戶身份信息,存入session 由於tomcat將session會序列化在本地硬盤上,所以使用Serializable接口
 * 
 * @author Thinkpad
 * 
 */
public class ActiveUser implements java.io.Serializable {
    private String userid;//用戶id(主鍵)
    private String usercode;// 用戶賬號
    private String username;// 用戶名稱

    private List<SysPermission> menus;// 菜單
    private List<SysPermission> permissions;// 權限

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }


    public String getUsercode() {
        return usercode;
    }

    public void setUsercode(String usercode) {
        this.usercode = usercode;
    }

    public String getUserid() {
        return userid;
    }

    public void setUserid(String userid) {
        this.userid = userid;
    }

    public List<SysPermission> getMenus() {
        return menus;
    }

    public void setMenus(List<SysPermission> menus) {
        this.menus = menus;
    }

    public List<SysPermission> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<SysPermission> permissions) {
        this.permissions = permissions;
    }


}

認證的服務


    @Override
    public ActiveUser authenticat(String userCode, String password)
            throws Exception {
        /**
    認證過程:
    根據用戶身份(賬號)查詢數據庫,如果查詢不到用戶不存在
    對輸入的密碼 和數據庫密碼 進行比對,如果一致,認證通過
         */
        //根據用戶賬號查詢數據庫
        SysUser sysUser = this.findSysUserByUserCode(userCode);

        if(sysUser == null){
            //拋出異常
            throw new CustomException("用戶賬號不存在");
        }

        //數據庫密碼 (md5密碼 )
        String password_db = sysUser.getPassword();

        //對輸入的密碼 和數據庫密碼 進行比對,如果一致,認證通過
        //對頁面輸入的密碼 進行md5加密 
        String password_input_md5 = new MD5().getMD5ofStr(password);
        if(!password_input_md5.equalsIgnoreCase(password_db)){
            //拋出異常
            throw new CustomException("用戶名或密碼 錯誤");
        }
        //得到用戶id
        String userid = sysUser.getId();
        //根據用戶id查詢菜單 
        List<SysPermission> menus =this.findMenuListByUserId(userid);

        //根據用戶id查詢權限url
        List<SysPermission> permissions = this.findPermissionListByUserId(userid);

        //認證通過,返回用戶身份信息
        ActiveUser activeUser = new ActiveUser();
        activeUser.setUserid(sysUser.getId());
        activeUser.setUsercode(userCode);
        activeUser.setUsername(sysUser.getUsername());//用戶名稱

        //放入權限範圍的菜單和url
        activeUser.setMenus(menus);
        activeUser.setPermissions(permissions);

        return activeUser;
    }

Controller處理認證,如果身份認證成功,那麼把認證信息存儲在Session中

    @RequestMapping("/login")
    public String login(HttpSession session, String randomcode,String usercode,String password)throws Exception{
        //校驗驗證碼,防止惡性攻擊
        //從session獲取正確驗證碼
        String validateCode = (String) session.getAttribute("validateCode");

        //輸入的驗證和session中的驗證進行對比 
        if(!randomcode.equals(validateCode)){
            //拋出異常
            throw new CustomException("驗證碼輸入錯誤");
        }

        //調用service校驗用戶賬號和密碼的正確性
        ActiveUser activeUser = sysService.authenticat(usercode, password);

        //如果service校驗通過,將用戶身份記錄到session
        session.setAttribute("activeUser", activeUser);
        //重定向到商品查詢頁面
        return "redirect:/first.action";
    }

身份認證攔截器



    //在執行handler之前來執行的
    //用於用戶認證校驗、用戶權限校驗
    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {

        //得到請求的url
        String url = request.getRequestURI();
        //判斷是否是公開 地址
        //實際開發中需要公開 地址配置在配置文件中
        //從配置中取逆名訪問url
        List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL");
        //遍歷公開 地址,如果是公開 地址則放行
        for(String open_url:open_urls){
            if(url.indexOf(open_url)>=0){
                //如果是公開 地址則放行
                return true;
            }
        }
        //判斷用戶身份在session中是否存在
        HttpSession session = request.getSession();
        ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
        //如果用戶身份在session中存在放行
        if(activeUser!=null){
            return true;
        }
        //執行到這裏攔截,跳轉到登陸頁面,用戶進行身份認證
        request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);

        //如果返回false表示攔截不繼續執行handler,如果返回true表示放行
        return false;
    }

授權攔截器


    //在執行handler之前來執行的
    //用於用戶認證校驗、用戶權限校驗
    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) throws Exception {

        //得到請求的url
        String url = request.getRequestURI();
        //判斷是否是公開 地址
        //實際開發中需要公開 地址配置在配置文件中
        //從配置中取逆名訪問url

        List<String> open_urls = ResourcesUtil.gekeyList("anonymousURL");
        //遍歷公開 地址,如果是公開 地址則放行
        for(String open_url:open_urls){
            if(url.indexOf(open_url)>=0){
                //如果是公開 地址則放行
                return true;
            }
        }
        //從配置文件中獲取公共訪問地址
        List<String> common_urls = ResourcesUtil.gekeyList("commonURL");
        //遍歷公用 地址,如果是公用 地址則放行
        for(String common_url:common_urls){
            if(url.indexOf(common_url)>=0){
                //如果是公開 地址則放行
                return true;
            }
        }
        //獲取session
        HttpSession session = request.getSession();
        ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser");
        //從session中取權限範圍的url
        List<SysPermission> permissions = activeUser.getPermissions();
        for(SysPermission sysPermission:permissions){
            //權限的url
            String permission_url = sysPermission.getUrl();
            if(url.indexOf(permission_url)>=0){
                //如果是權限的url 地址則放行
                return true;
            }
        }

        //執行到這裏攔截,跳轉到無權訪問的提示頁面
        request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(request, response);

        //如果返回false表示攔截不繼續執行handler,如果返回true表示放行
        return false;
    }

攔截器配置:



    <!--攔截器 -->
    <mvc:interceptors>

        <mvc:interceptor>
            <!-- 用戶認證攔截 -->
            <mvc:mapping path="/**" />
            <bean class="cn.itcast.ssm.controller.interceptor.LoginInterceptor"></bean>
        </mvc:interceptor>
        <mvc:interceptor>
            <!-- 授權攔截 -->
            <mvc:mapping path="/**" />
            <bean class="cn.itcast.ssm.controller.interceptor.PermissionInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章