Shiro教程之三自定義Realm認證和授權(結合數據庫)


Shiro默認使用自帶的IniRealm,IniRealm從ini配置文件中讀取用戶的信息,大部分情況下需要從系統的數據庫中讀取用戶信息,所以需要自定義realm。

1,Realm接口 

 

 

最基礎的是Realm接口,CachingRealm負責緩存處理,AuthenticationRealm負責認證,AuthorizingRealm負責授權,通常自定義的realm繼承AuthorizingRealm

 

2,數據表設計

 

 

permission 菜單和權限表
role 角色表
role_permission 角色和權限的關系表
user 用戶表
user_role用戶和角色之間的關系表

 

 

3,實現步驟

1,創建shiro_realm的maven項目

web.xml文件過濾器配置:

<!-- Shiro filter start -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>
            org.springframework.web.filter.DelegatingFilterProxy
        </filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
        <!-- 下面這個參數表示,applicationContext.xml(Shiro過濾器鏈)的名字,如果省略,配置器鏈名默認就是過濾器名字-->
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>shiroFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- Shiro filter end -->

 

applicationContext.xml設置

    <!-- 10. Shiro認證權限配置-->
    <!-- ================ Shiro start ================ -->

    <!-- (1).聲明憑證匹配器(密碼加密用)-->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <!--注入算法-->
        <property name="hashAlgorithmName" value="md5"></property>
        <!--注入散列次數-->
        <property name="hashIterations" value="1"></property>
    </bean>

    <!-- (2).配置Realm-->
    <bean id="shiroReaml" class="com.cc8w.shiro.ShiroRealm">
        <!--注入憑證匹配器-->
        <property name="credentialsMatcher" ref="credentialsMatcher"></property>
    </bean>

    <!-- (3). 創建安全管理器-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!--注入reaml-->
        <property name="realm" ref="shiroReaml"></property>
    </bean>

    <!-- (4). 配置過濾器鏈-->
    <!-- Shiro 的Web過濾器 id必須和web.xml里面的shiroFilter的 targetBeanName的值一樣 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!--Shiro的核心安全接口,這個屬性是必須的-->
        <property name="securityManager" ref="securityManager"></property>
        <!-- 要求登錄時的鏈接(登錄頁面地址),非必須屬性,屬性會自動尋找web工程根目錄下的"/login.jsp"頁面-->
        <property name="loginUrl" value="/login.jsp"></property>
        <!-- Shiro 的Web過濾器 id必須和web.xml里面的shiroFilter的 targetBeanName的值一樣 -->
        <!--property name="successUrl" value="success.do"></property-->
        <!-- 用戶訪問未對其授權的資源時,所顯示的連接 -->
        <property name="unauthorizedUrl" value="unauthorized.jsp"></property>
        <!-- 過濾器鏈的定義,從上往下順序執行,一般將/**放在最后-->
        <property name="filterChainDefinitions">
            <value>
                <!-- /**=authc 所有url都必須認證通過才可以訪問 -->
                /index.jsp*=anon
                /login/toLogin*=anon
                /login/login*=anon
                <!-- 如果訪問/login/logout就是用Shiro注銷session-->
                /login/logout=logout
                <!-- /** = anon所有url都可以匿名訪問 -->
                <!-- /** = authc -->
                <!-- /*/* = authc -->
                <!-- /** = authc所有url都不可以匿名訪問 必須放到最后面 -->
                /** = authc
            </value>
        </property>
    </bean>
    <!-- ================ Shiro end ================ -->

 

2,創建自定義realm (這個里面重寫兩個方法一個認證回調,一個授權回調)

package com.cc8w.shiro;


import com.cc8w.entity.UserActivePojo;
import com.cc8w.entity.UserPojo;
import com.cc8w.service.PermssionService;
import com.cc8w.service.RoleService;
import com.cc8w.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;


/**
 * Shiro默認使用自帶的IniRealm,IniRealm從ini配置文件中讀取用戶的信息,大部分情況下需要從系統的數據庫中讀取用戶信息,所以需要自定義realm。
 * 最基礎的是Realm接口,CachingRealm負責緩存處理,AuthenticationRealm負責認證,AuthorizingRealm負責授權,通常自定義的realm繼承AuthorizingRealm
 */
@Component
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private PermssionService permssionService;
    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }



    /*
     * 登錄信息和用戶驗證信息驗證(non-Javadoc)
     * @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //1.從authenticationToken中獲取身份信息,其實就是用戶的登錄名
        String username = authenticationToken.getPrincipal().toString();
        String password = authenticationToken.getCredentials().toString();

        //2.根據用戶名查詢用戶是否存在
        UserPojo user=userService.queryUserByUserName(username);
        System.out.println(user);
        //返回null說明用戶不存在
        if(null!=user) {
            //2.1根據用戶名去查詢用戶擁有哪些角色
            List<String> roles= roleService.queryRolesByUserName(user.getUserName());
            System.out.println(roles);
            //2.2根據用戶名查詢用戶擁有哪些權限
            List<String> permissions=permssionService.queryPermissionsByUserName(user.getUserName());

            UserActivePojo activeUser=new UserActivePojo(user, roles, permissions);

           //3.返回認證信息
            /**
             * 參數1  用戶身份
             * 參數2 用戶在數據庫里面存放的密碼
             * 參數3 當前類名
             */
            SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(activeUser, user.getPassword(), this.getName());
            //SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, realmName)
            return info;

        }
        return null;
    }

    /*
     * 授權查詢回調函數, 進行鑒權但緩存中無用戶的授權信息時調用,負責在應用程序中決定用戶的訪問控制的方法(non-Javadoc)
     * @see org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //1.獲得用戶身份信息(PrincipalCollection有認證回調傳來的第一個參數[activeUser])
        UserActivePojo activeUser = (UserActivePojo) principalCollection.getPrimaryPrincipal();
        System.out.println("doGetAuthorizationInfo");
        //2.根據身份信息獲取權限數據
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();

        //3.根據用戶查詢用戶的角色 (其實認證方法一並查詢出來了,保存在UserActivePojo)
        List<String> roles = activeUser.getRoles();
        if(null!=roles&&roles.size()>0) {
            info.addRoles(roles);//添加角色
        }
        //4.根據用戶查詢用戶的權限
        List<String> permissions=activeUser.getPermissions();
        if(null!=permissions&&permissions.size()>0) {
            info.addStringPermissions(permissions);//添加權限
        }

        /**
         * 總結:本來授權->查角色和權限都在本方法寫(但是前端每查詢一次權限,就會回調本方法一次,
         * 所以直接查數據庫,對數據庫有壓力),所以最后,
         * 1.把查角色和權限方法寫在了認證,然后封裝成activeUser傳遞過來,這樣Controller每次查權限,就不用查數據庫了,直接在activeUser獲取即可.
         * 2.緩存應該也可以解決
         */


        return info;
    }


}

 

4,測試 (認證和授權)

package com.cc8w.test;



import com.cc8w.shiro.ShiroRealm;
import org.apache.log4j.Logger;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Arrays;

/**
 *
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class TestShiro {
    private static Logger logger = Logger.getLogger(TestShiro.class);

    @Autowired
    private ShiroRealm shiroRealm;

    public static void main(String[] args) {
        TestShiro ts = new TestShiro();
        ts.testAuth();


    }


    //三,登錄測試(自定義Realm)
    @Test
    public void testRealmLogin(){
        //1.創建一個安全管理器的工廠
        Factory<SecurityManager> factory = new IniSecurityManagerFactory();
        //2.在工廠中獲取安全管理器
        DefaultSecurityManager securityManager = (DefaultSecurityManager) factory.getInstance();

        //2.1 創建自定義Realm注入到安全管理器
        //ShiroRealm shiroRealm = new ShiroRealm();//(SpringM在bean控制,xml也可以配置散列加密相關)
        securityManager.setRealm(shiroRealm);

        //3.將securityManager綁定到運行環境
        SecurityUtils.setSecurityManager(securityManager);
        //4.獲取Subject對象(將要登錄的用戶)
        Subject subject = SecurityUtils.getSubject();
        //5.獲取要登錄用戶的token,客戶端傳遞過來的用戶名和密碼
        String username = "zhangsan",password="123456";
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);

        try{
            //6.登陸(認證)
            subject.login(token);
            logger.info("登錄了");

        }catch (IncorrectCredentialsException  e ){
            logger.info("密碼不正確");
            logger.info(e);
        }catch (UnknownAccountException e) {
            System.out.println("沒有這個帳號");
        }catch (AuthenticationException e) {
            e.printStackTrace();
        }

        //如果登錄成功了,可以獲取subject中各種狀態了
        Boolean isAuth = subject.isAuthenticated();
        System.out.println("認證狀態:" + isAuth);

        // 7.授權 分為:基於角色授權 基於資源的授權
        //7.1 基於角色授權
        boolean permited  = subject.hasRole("role1");
        System.out.println("這是授權單個:"+permited);
        boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("role1","role2","role3"));
        System.out.println("這個授權多個"+hasAllRoles);

        // 使用check方法進行授權,如果授權不通過會拋出異常
        // subject.checkRole("role13");
        try {
            subject.checkRole("roles1");
        }catch (UnauthenticatedException e){
            logger.info("沒有這個角色");
            //e.printStackTrace();
        }catch (UnauthorizedException e){
            logger.info("沒有這個權限");
            //e.printStackTrace();
        }


        //7.2 基於資源的授權
        //isPermitted傳入權限標識符
        boolean isPermitted = subject.isPermitted("user:query");
        System.out.println("單個權限判斷:"+isPermitted);

        boolean isPermittedAll = subject.isPermittedAll("user:query","user:adb","user:add");
        System.out.println("多個權限判斷"+isPermittedAll);

        // 使用check方法進行授權,如果授權不通過會拋出異常
        try {
            subject.checkPermission("user:adb");
        }catch (UnauthenticatedException e){
            logger.info("沒有這個角色");
            //e.printStackTrace();
        }catch (UnauthorizedException e){
            logger.info("沒有這個權限");
            //e.printStackTrace();
        }





    }

    //四,授權驗證(自定義Realm)
    public void testRealmAuth(){
        //其實授權也需要登陸(上面方法第7條之后:就是授權的驗證)

    }


}

 

 

前提是: 已經在service和mapper里面寫了,角色和權限的查詢!! 如下:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM