天天看點

Shiro的認證和權限控制

權限控制的方式

從類别上分,有兩大類:

- 認證:你是誰?–識别使用者身份。

- 授權:你能做什麼?–限制使用者使用的功能。

權限的控制級别

從控制級别(模型)上分:

- URL級别-粗粒度

- 方法級别-細粒度

- 頁面級别-自定義标簽(顯示)

- 資料級别-最細化的(資料)

URL級别的權限控制-粗粒度

在web.xml中配置一個過濾器filter,在過濾器中,對請求的位址進行解析,字元串截取:

url.substring()…把上下文前面的路徑都截取掉,剩下user_login.action。

過濾器代碼:

以通過查詢資料庫,來判斷,目前登入使用者,是否可以通路user_login.action。

url級别控制,每次請求過程中隻控制一次 ,相比方法級别權限控制 是粗粒度的 !URL級别權限控制,基于Filter實作。

方法級别的權限控制-細粒度

aop面向切面的程式設計,在方法執行之前,進行權限判斷,如果沒有權限,抛出異常,終止方法的繼續運作。

自定義注解 在需要權限控制方法上, 添加需要的權限資訊

代理 (Spring AOP ),在目标方法運作時 進行增強 ,通過反射技術擷取目标方法上注解中權限 , 查詢資料庫擷取目前登陸使用者具有權限,進行比較。

相比URL級别權限控制, 可以控制到伺服器端執行的每個方法,一次請求中可以控制多次。

頁面(顯示)級别的權限控制-自定義标簽

頁面顯示的權限控制,通常是通過 自定義标簽來實作

資料級别的權限控制

在每條資料上增加一個字段,該字段記錄了權限的值。資料和權限綁定。

代碼,你在查詢資料的時候,需要去權限和使用者對應表中,通過目前登入使用者的條件,查詢出你的資料權限。然後再将資料權限作為一個條件,放到業務表中進行查詢。進而限制了資料的通路。

權限系統的資料表設計

    資源:使用者要通路的目标,通常是服務中的程式或檔案

    權限:使用者具有通路某資源的能力

    角色:權限的集合,為了友善給使用者授權。

    使用者:通路系統的’人’。

表對象實體:

- 使用者(User)表:通路系統的使用者,比如使用者登入要用

- 權限(Function)表:系統某個功能允許通路而對應的權限

- 角色(Role)表:角色是權限的集合(權限組),友善使用者授權。

表對象之間的關系:

- 使用者和角色關系表:一個使用者對應N個角色,一個角色可以授予N個使用者—》多對多關系

- 角色和權限關系表:一個角色包含N個權限,一個權限可以屬于N個角色—》多對多關系

完整的權限相關表:

URL級别權限控制包含:資源表、權限表、角色表、使用者表,以及相關關系(都是多對多),共7張表。

方法級别的權限控制包含:功能權限、角色、使用者,以及相關關系(都是多對多),共5張表。

但Apache Shiro架構支援的URL級别權限控制,是将資源和資源權限對應關系配置到了配置檔案中,不需要表的支撐,隻需要5張表了。

Apache Shiro權限控制

Apache Shiro 可以不依賴任何技術使用, 可以直接和web整合,通常在企業中和Spring 結合使用。

Authentication: 認證 — 使用者登入

Authorization : 授權 —- 功能權限管理

通過引入Maven坐标導入shiro

官方建議:不推薦直接引入shiro-all,依賴比較多,原因怕有jar沖突。官方推薦根據需要單獨導入jar。

Shiro基本原理

Shiro的架構的體系結構:

Shiro權限控制流程的原理:

    應用代碼 —- 調用Subject (shiro的Subject 就代表目前登陸使用者) 控制權限 —- Subject 在shiro架構内部 調用 Shiro SecurityManager 安全管理器 —– 安全管理器調用 Realm (程式和安全資料連接配接器 )。

    Subject要進行任何操作,都必須要調用安全管理器(對我們來說是自動的)。

    而安全管理器會調用指定的Realms對象,來連接配接安全資料。

    Realms用來編寫安全代碼邏輯和通路安全資料,是連接配接程式和安全資料的橋梁。

URL級别的權限控制

配置整合和url級别認證

配置過濾器web.xml:放在struts的前端控制器之前配置,但放在openEntitymanage之後。

    <!-- shiro權限過濾器 -->

        <filter>

            <!-- 這裡的 filter-name 要和 spring 的 applicationContext-shiro.xml 裡的 org.apache.shiro.spring.web.ShiroFilterFactoryBean

                的 bean name 相同 -->

            <filter-name>shiroSecurityFilter</filter-name>

            <!-- spring的代理過濾器類:以前的過濾器 -->

            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>

            <init-param>

                <!-- 該值預設為false,表示生命周期由SpringApplicationContext管理,設定為true則表示由ServletContainer管理 -->    

                <param-name>targetFilterLifecycle</param-name>

                <param-value>true</param-value>

            </init-param>

        </filter>

        <filter-mapping>

            <filter-name>shiroSecurityFilter</filter-name>

            <url-pattern>

        @Component("bosRealm")

        public class BosRealm extends AuthorizingRealm{

            //注入ehcache的緩存區域

            @Value("BosShiroCache")//注入緩存具體對象的名字,該名字在ehcache.xml中配置的

            public void setSuperAuthenticationCacheName(String authenticationCacheName){

                super.setAuthenticationCacheName(authenticationCacheName);

            }

            //注入service

            @Autowired

            private UserService userService;

            //注入角色dao

            @Autowired

            private RoleDao roleDao;

            //注入功能的dao

            @Autowired

            private FunctionDao functionDao;

            //授權方法:擷取使用者的權限資訊

            //授權:回調方法

            //如果傳回null,說明沒有權限,shiro會自動跳到<property name="unauthorizedUrl" value="/unauthorized.jsp" />

            //如果不傳回null,根據配置/page_base_subarea.action = roles["weihu"],去自動比對

            //給授權提供資料的

            @Override

            protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

                //給目前使用者授權的權限(功能權限、角色)

                SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

                //兩種方式:

                //方式1:工具類來擷取(首長-)

        //      User user=(User)SecurityUtils.getSubject().getPrincipal();

                //方式2:通過參數擷取首長(推薦)

                User user = (User) principals.getPrimaryPrincipal();

                //實際:需要根據目前使用者的角色和功能權限來建構一個授權資訊對象,交給安全管理器

                if (user.getUsername().equals("admin")) {

                    //如果是超級管理者

                    //查詢出所有的角色,給認證資訊對象

                    List<Role> roleList = roleDao.findAll();

                    for (Role role : roleList) {

                        authorizationInfo.addRole(role.getCode());

                    }

                    //查詢出所有的功能權限,給認證對象

                    List<Function> functionList = functionDao.findAll();

                    for (Function function : functionList) {

                        authorizationInfo.addStringPermission(function.getCode());

                    }

                } else {

                    //如果是普通使用者

                    List<Role> roleList = roleDao.findByUsers(user);

                    for (Role role : roleList) {

                        authorizationInfo.addRole(role.getCode());

                        //導航查詢,擷取某角色的擁有的功能權限

                        Set<Function> functions = role.getFunctions();

                        for (Function function : functions) {

                            authorizationInfo.addStringPermission(function.getCode());

                        }

                    }

                }

                return authorizationInfo;//将授權資訊交給安全管理器接口。

            }

            //認證:回調,認證管理器會将認證令牌放到這裡(action層的令牌AuthenticationToken)

            //發現如果傳回null,抛出使用者不存在的異常UnknownAccountException

            @Override

            protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

                //使用者名密碼令牌(action傳過來)

                UsernamePasswordToken upToken = (UsernamePasswordToken) token;

                //調用業務層來查詢(根據使用者名來查詢使用者,無需密碼)

                User user = userService.findByUsername(upToken.getUsername());

                //判斷使用者是否存在

                if (user == null) {

                    //使用者不存在

                    return null;//抛出異常

                } else {

                    //使用者名存在

                    //參數1:使用者對象,将來要放入session,資料庫查詢出來的使用者

                    //參數2:憑證(密碼):密碼校驗:校驗的動作交給shiro

                    //參數3:目前使用的Realm在Spring容器中的名字(bean的名字,自動在spring容器中尋找)

                    SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), super.getName());

                    return authenticationInfo;//密碼校驗失敗,會自動抛出IncorrectCredentialsException

                }

            }

        }

ApplicatonContext.xml:

    <!-- service需要spring掃描 -->

    <context:component-scan base-package="cn.aric.bos.service,cn.aric.bos.web,cn.aric.bos.auth.realm" />

    <!-- shiro安全管理器 -->

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

        <!-- 注入 Realm連接配接安全資料-->

        <property name="realm" ref="bosRealm"></property>

        <!-- 注入shiro的緩存管理器 -->

        <property name="cacheManager" ref="shiroCacheManager"/>

    </bean>

使用者認證(退出)以及修改密碼

        @Action(value="user_logout",results={@Result(name=LOGIN,type="redirect",location="/login.jsp")})

        public String logout() throws Exception {       

            //shiro退出

            Subject subject = SecurityUtils.getSubject();

            subject.logout();

            //跳轉登陸頁面

            return LOGIN;

        }

    //  @Action(value="user_editPassword",results={@Result(name=JSON,type=JSON)})

        @Action("user_editPassword")

        public String editPassword() throws Exception {

            //擷取Principal就是擷取目前使用者

            User loginUser = (User) SecurityUtils.getSubject().getPrincipal();

            model.setId(loginUser.getId());

            //頁面結果

            HashMap<String,Object> resultMap = new HashMap<String,Object>();

            try {

                //調用service進行修改密碼

                userService.updateUserPassword(model);

                //修改成功

                resultMap.put("result", true);

            } catch (Exception e) {

                e.printStackTrace();

                //修改失敗

                resultMap.put("result", false);

            }

            //将結果壓入棧頂

            ActionContext.getContext().getValueStack().push(resultMap);

            //轉換為json

            return JSON;

        }

使用者授權(授權)—自定義Ream

資料庫資料添加,applicationContext.xml配置

        <property name="filterChainDefinitions">

            <value>

                /login.jsp = anon

                /validatecode.jsp = anon

                /js/** = anon

                /css/** = anon

                /images/** = anon

                /user_login.action = anon

                /page_base_staff.action = anon

                /page_base_region.action = perms["region"]

                /page_base_subarea.action = roles["weihu"]

                /page_qupai_noticebill_add.action = perms["noticebill"]

                /page_qupai_quickworkorder.action = roles["kefu"]

                /** = authc

            </value>

        </property>

代碼在上面的BosRealm的中,protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)

RoleDao省略。

方法級别的權限控制

啟用Shiro注解

需要 Shiro 的 Spring AOP 內建來掃描合适的注解類以及執行必要的安全邏輯。

ApplicationContext.xml

        <!-- 開啟權限控制的注解功能并且配置aop -->

        <!-- 後處理器:通過動态代理在某bean執行個體化的前增強。:自己去找權限注解 -->

        <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

        <!-- 切面自動代理:相當于以前的AOP标簽配置

        advisor:切面 advice:通知

        -->

        <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"

        depends-on="lifecycleBeanPostProcessor">

        </bean>

        <!-- Advisor切面配置:授權屬性的切面 -->

        <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">

            <!-- 注入安全管理器 -->

            <property name="securityManager" ref="securityManager"/>

        </bean>

在需要權限控制的目标方法上面使用shiro的注解:

@RequiresAuthentication 需要使用者登入

subject.isAuthenticated() 必須傳回true

@ RequiresUser

subject.isAuthenticated() 傳回true 或者subject.isRemembered() 傳回true

“Remember Me”服務:

認證機制 基于 session

被記憶機制 基于 cookie (subject.isAuthenticated() 傳回 false )

@ RequiresGuest 與 @RequiresUser 相反,不能認證也不能被記憶。

@ RequiresRoles 需要角色

@RequiresPermissions 需要權限

異常

動态代理異常

解決方案:

配置ApplicationContext.xml,設定代理為cglib代理(對目标類代理)

        <!-- 切面自動代理:相當于以前的AOP标簽配置 -->

        <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"

        depends-on="lifecycleBeanPostProcessor" >

            <!-- 設定aop的代理使用CGLIB代理 -->

            <property name="proxyTargetClass" value="true"/>

        </bean>

方案二:

<aop:config proxy-target-class="true" />

    1

類型轉換異常

解決方案:遞歸向上尋找泛型的類型。

    //遞歸向上 查找

        Class actionClass =this.getClass();

        //向父類遞歸尋找泛型

        while(true){

            //得到帶有泛型的類型,如BaseAction<Userinfo>

            Type type = actionClass.getGenericSuperclass();

            if(type instanceof ParameterizedType){

                //轉換為參數化類型

                ParameterizedType parameterizedType = (ParameterizedType) type;

                //擷取泛型的第一個參數的類型類,如Userinfo

                Class<T> modelClass = (Class<T>) parameterizedType.getActualTypeArguments()[0];

                //執行個體化模型對象

                try {

                    model=modelClass.newInstance();

                } catch (InstantiationException e) {

                    e.printStackTrace();

                } catch (IllegalAccessException e) {

                    e.printStackTrace();

                }

                break;

            }

            //尋找父類

            actionClass=actionClass.getSuperclass();

        }

空指針異常

解決方案1:使用public 的Setter方法上的注解直接注入Service。

SubareaAction:

        //注入service

        private SubareaService subareaService;

        @Autowired

        public void setSubareaService(SubareaService subareaService) {

            this.subareaService = subareaService;

        }

解決方案2:

@Autowire還放到私有聲明上,

在struts.xml中覆寫常量(開啟自動裝配政策):

值預設是false,struts2預設注入采用的是構造器注入(從spring中尋找的bean)

改成true,struts2會采用setter方法注入

頁面标簽(實作頁面内容定制顯示)

        <!-- 引入Shiro标簽 -->

        <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>

    頁面拿Session中的user對象: 代表user對象。

    程式中拿Session中的user對象:SecurityUtils.getSubject().getPrincipal()

資源通配符和權限通配符可便捷開發。

代碼級别

使用代碼程式設計的方式,直接在程式中使用Subject對象,調用内部的一些API。(有代碼侵入)

    //代碼級别的權限控制(授權):功能權限和角色權限:兩套機制:boolean判斷,異常判斷

        //授權的權限控制

        //====布爾值判斷

        //功能權限

        if(subject.isPermitted("staff")){

            //必須擁有staff功能權限才能執行代碼

            System.out.println("我是一段代碼。。。。。");

        }

        //角色權限

        if(subject.hasRole("weihu")){

            //必須擁有staff功能權限才能執行代碼

            System.out.println("我是一段代碼。。。。。");

        }

        //====異常判斷

        //功能權限

        try {

            subject.checkPermission("staff");

            //有權限

        } catch (AuthorizationException e) {

            // 沒權限

            e.printStackTrace();

        }

        //角色權限

        try {

            subject.checkRole("weihu");

            //有權限

        } catch (AuthorizationException e) {

            // 沒權限

            e.printStackTrace();

        }

---------------------

作者:宏微

來源:CSDN

原文:https://blog.csdn.net/shuaicihai/article/details/58391481

版權聲明:本文為部落客原創文章,轉載請附上博文連結!

繼續閱讀