spring整合shiro系列(三)spring boot 整合 shiro

之前寫了兩篇有關shiro和spring的文章,結合目前java開發spring boot框架是大勢,所以有必要將shiro和spring boot進行一次整合。我在使用spring  boot的時候比較明顯的一個感觸就是原來spring的xml配置後面都使用java bean的形式替代了。總體看java代碼的程度更加純粹了。

首先看一下依賴

<!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
<!--redis-->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.2.3</version>
        </dependency>

第一個依賴是spring和shiro的依賴,相對於目前來說,版本不算新的。第二個是一個插件主要是使用redis去緩存shiro的一些授權認證信息。

然後就是shiro的配置過程。我的話喜歡把安全框架的內容放在一個包裏面,這樣感覺會更加清晰一點

第一步新建是個ShiroConfig。同時加上Configuration註解將它標記成一個配置類。然後我們定義幾個變量

這裏的變量主要是關聯redis的配置,比如ip,祕密,端口,過期時間。可以將值寫在配置文件yml裏,也可以直接賦值。

這個是shiro官網整合spring時的配置(xml版)。大致我們可以看到這是定義了一個自定義的shiroFilter(過濾器)

裏面配置了一些參數,主要時登錄路由,登錄成功路由,不用授權的路由,自定義的過濾器鏈,和自定義的過濾器,

還有就是securityManager安全管理器

下面的是spring boot版Java bean的格式。我們可以看到大多的參數只是換了一個形式而已。首先實例化了一個過濾器工廠,添加了兩個自定義的過濾器分別是處理跨域和登錄攔截的。然後聲明瞭一個map,裏面的key代表的是我們系統請求時用到的路由,而value代表的就是這個路由所需要的權限。這裏的anon代表遊客權限,具體可以看之前的博客文章。這裏主要是放行了一些靜態資源比如swagger接口文檔和一些登錄接口。

 /**
     * @description 過濾器
     * @author zhou
     * @created  2019/3/13 15:47
     * @param
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shrioFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //自定義過濾器
        Map<String,Filter> filterMap = new LinkedHashMap<String,Filter>();
        filterMap.put("cros",crosFilter());
        filterMap.put("login",logInterceptor());
        shiroFilterFactoryBean.setFilters(filterMap);
        //攔截器
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/v2/**", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**", "anon");
        filterChainDefinitionMap.put("/back/admin/login/**","anon");
        filterChainDefinitionMap.put("/back/admin/sendCode/**","anon");
        filterChainDefinitionMap.put("/back/admin/forgetPassword/**","anon");
        filterChainDefinitionMap.put("/shop/**","anon");
        //filterChainDefinitionMap.put("/back/**","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

然後比較重要的就是這個安全管理器了具體的授權邏輯在這裏展開。這裏主要是配置了三個東西第一個realm就是授權和驗證的實現,第二個是session管理(會話管理,判斷你是不是在一個會話裏),第三個是緩存管理

   /**
     * @description 安全管理器
     * @author zhou
     * @created  2019/3/13 15:42
     * @param
     * @return
     */
    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        securityManager.setSessionManager(sessionManager());
        securityManager.setCacheManager(redisCacheManager());
        return securityManager;
    }

先看這個realm,還是比較簡單的主要是兩個方面,一是用戶登錄授權和驗證的具體實現,二就是加密策略。下面來看下代碼實現

realm這個和之前xml版的沒有什麼區別,我們創建一個MyShiroRealm.class然後繼承AuthorizingRealm。這裏主要是重寫父類中授權和驗證這兩個方法。不同系統裏面的內部業務邏輯可能會有所不同,但整體的思路是一樣的。

第一個方法是授權,主要做的就是通過用戶名去調用內部的業務邏輯獲取你的角色和權限集合,然後將你的角色和權限存入到SimpleAuthorizationInfo 這個對象中,那麼在後續調用方法的時候就可以聯動註解進行權限的判斷。一般可以將String類型的角色和權限存入。

第二個是登錄的驗證。這個方法主要是通過用戶名去你的數據庫中獲取這個用戶,然後對於這個用戶可以先做一些禁用,過期的校驗,然後將用戶名密碼封裝後返回,交由shiro做後面的密碼匹配。

 /**
     * @description 爲當前登錄的用戶授予角色和權限
     * @author zhou
     * @created  2019/4/30 15:03    
     * @param 
     * @return 
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //獲取用戶名
        String name = (String) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //查詢用戶的角色
        Set<String> roles = roleService.getRoleNameByUserName(name);
        authorizationInfo.setRoles(roles);
        return authorizationInfo;
    }

    /**
     * @description 驗證當前的用戶
     * @author zhou
     * @created  2019/4/30 16:28
     * @param 
     * @return 
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //獲取用戶信息
        String name = (String) authenticationToken.getPrincipal();
        //從數據庫中查找
        TbUser tbUser = userMapper.getUserByName(name);
        if(null == tbUser){
            //賬號不存在
            throw new UnknownAccountException();
        }else if(tbUser.getDeleted()){
            //賬號被禁用
            throw new LockedAccountException();
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(tbUser.getName(),
                tbUser.getPassword(), ByteSource.Util.bytes(tbUser.getSalt()),getName());

        return authenticationInfo;
    }
密碼工具類的話也是需要配置在自定義的Realm中
/**
     * @description 自定義Realm
     * @author zhou
     * @created  2019/3/13 15:43
     * @param
     * @return
     */
    @Bean
    public MyShiroRealm myShiroRealm(){
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }

具體的密碼類如下,這裏使用一個hash的密碼工具類,定義了加密的算法和加密次數,對於密碼使用了加鹽處理。當我們之前的驗證方法返回後,shiro就會去處理密碼比對,這裏就是根據你realm中配置的密碼匹配器去做對應的處理

 /**
     * @description 密碼比較器
     * @author zhou
     * @created  2018/12/27 9:34
     * @param
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashIterations(2);
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        return hashedCredentialsMatcher;
    }
 /**
     * @description 登錄密碼加鹽
     * @author zhou
     * @created  2019/3/14 20:23
     * @param
     * @return
     */
    public TbUser encryptPassword(TbUser tbUser){
        if(null == tbUser.getSalt() || "".equals(tbUser.getSalt())){
             tbUser.setSalt(UUID.randomUUID().toString().replace("-",""));
        }
        String password = new SimpleHash(hashedCredentialsMatcher.getHashAlgorithmName(),tbUser.getPassword(),
                ByteSource.Util.bytes(tbUser.getSalt()),hashedCredentialsMatcher.getHashIterations()).toHex();
        tbUser.setPassword(password);
        return tbUser;
    }

    /**
     * @description 加密
     * @author zhou
     * @created  2019/5/13 15:39
     * @param password 密碼
     * @param salt 鹽
     * @return
     */
    public String getNewPassword(String password,String salt){
        String newPassword =  new SimpleHash(hashedCredentialsMatcher.getHashAlgorithmName(),password,
                ByteSource.Util.bytes(salt),hashedCredentialsMatcher.getHashIterations()).toHex();
        return newPassword;
    }

 上述的用戶授權和登錄驗證。是通過下面這個方法經過shiro的層層調用進入的,具體大家斷點一下就知道了,首先你需要在登錄接口中獲取當前會話的主體就是這個第一行代碼,然後你需要將你的用戶名和密碼封裝成shiro認可的UsernamePasswordToken對象,這個對象簡單理解就是一個憑證。然後用主體把憑證作爲參數調用login,後面就會去調用你自定義realm中的重寫方法。

//獲取主體
        Subject currentUser = SecurityUtils.getSubject();
        //判斷用戶是否登陸
        UsernamePasswordToken token = new UsernamePasswordToken(loginParam.getName(),loginParam.getPassword());

  currentUser.login(token);

下面說一下安全管理器另外配置的兩個東西sessionManager和cacheManager(會話管理和緩存管理)。這個會話管理可以有不同的保存載體比如內存或redis.

這裏我們看一下文檔中對於會話管理器的定義和闡述,大致意思就是管理器用於對會話信息的增刪改查,信息用於首次登錄後程序的調用,用戶可以選擇數據庫(mysql)或者Nosql(redis等)來做存儲。

我這裏使用redis來做session管理的數據訪問層,因爲shiro默認的會話是基於服務器內存的不適合生產環境,所以用redis做持久化,這裏配置了一個RedisManager由會話管理和緩存管理共用。通用的配置也比較簡單就是redis的序列化方式,端口之類的。

  /**
     * @description shiro-redis開源插件
     * @author zhou
     * @created  2019/3/22 17:01
     * @param
     * @return
     */
    @Bean
    public RedisManager redisManager(){
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host+":"+port);
        redisManager.setPassword(password);
        redisManager.setTimeout(timeout);
        return redisManager;
    }
/**
     * @description sessionDao層的實現
     * @author zhou
     * @created  2019/3/22 17:03
     * @param
     * @return
     */
    @Bean
    public RedisSessionDAO redisSessionDAO(){
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setKeySerializer(new StringSerializer());
        redisSessionDAO.setValueSerializer(new ObjectSerializer());
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }
然後我們查看這個RedisSessionDAO的源碼,可以看到他具體緩存了什麼信息。這裏應該是以sessionId爲key,緩存了整個session的信息,

然後說一下緩存管理器,我最初也是比較模糊既然會話管理已經用了redis,爲什麼又整了一個緩存管理?其實按照文檔的意思,會話管理僅僅是處理用戶的登錄和交互的session,而緩存管理器是緩存所有shiro需要緩存的數據的。同時當你配置了cacheManager的時候,sessionDAO在做持久化的時候會調用你的cacheManager。這是我對這個文檔的一些理解

後面的話就是再增加一個激活註解的配置,這樣就可以在方法上增加鑑權的註解

/**
     * @description 註解啓用
     * @author zhou
     * @created  2018/12/27 9:35
     * @param
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

到這裏的話,spring  boot 結合shiro大致的框架脈絡已經清晰了,當然還有一些註解使用,登出,和源碼的細節,由於篇幅有限就不擴展了。希望對大家使用spring boot 來整合shiro有所幫助

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