Shiro許可權管理框架(一):Shiro的基本使用

夜月歸途發表於2019-07-28

首發地址:https://www.guitu18.com/post/2019/07/26/43.html

核心概念

Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。使用Shiro的易於理解的API,您可以快速、輕鬆地獲得任何應用程式,從最小的移動應用程式到最大的網路和企業應用程式。

上面這段話來自百度百科,是不是非常官方,好像說的很明白但是又好像什麼都沒說的樣子,到底是個啥呀。想要快速理解並使用Shiro先要從最重要的三大概念入手。

  1. Subject:大白話來講就是使用者(當然並不一定是使用者,也可以指和當前應用互動的任何物件),我們在進行授權鑑權的所有操作都是圍繞Subject(使用者)展開的,在當前應用的任何地方都可以通過SecurityUtils的靜態方法getSubject()輕鬆的拿到當前認證(登入)的使用者。
  2. SecurityManager:安全管理器,Shiro中最核心的元件,它管理著當前應用中所有的安全操作,包括Subject(使用者),我們圍繞Subject展開的所有操作都需要與SecurityManager進行互動。可以理解為SpringMVC中的前端控制器。
  3. Realms:字面意思為領域,Shiro在進行許可權操作時,需要從Realms中獲取安全資料,也就是使用者以及使用者的角色和許可權。配置Shiro,我們至少需要配置一個Realms,用於使用者的認證和授權。通常我們的角色及許可權資訊都是存放在資料庫中,所以Realms也可以算是一個許可權相關的Dao層,SecurityManager在進行鑑權時會從Realms中獲取許可權資訊。

這三個基本的概念簡答理解後就可以開始配置和使用Shiro了,其實Shiro最基本的使用非常簡單,加入依賴後只需要配置兩個Bean,再繼承一個抽象類實現兩個方法即可。


基本使用

引入一個依賴

新建一個基於Springboot的Web專案,引入Shiro依賴。

        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-web -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

配置兩個Bean

新建一個Shiro配置類,配置Shiro最為核心的安全管理器SecurityManager。

    @Bean
    public SecurityManager securityManager(UserAuthorizingRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        securityManager.setRememberMeManager(null);
        return securityManager;
    }

再配置Shiro的過濾器工廠類,將上一步配置的安全管理器注入,並配置相應的過濾規則。

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 登入頁面,無許可權時跳轉的路徑
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 配置攔截規則
        Map<String, String> filterMap = new HashMap<>();
        // 首頁配置放行
        filterMap.put("/", "anon");
        // 登入頁面和登入請求路徑需要放行
        filterMap.put("/login", "anon");
        filterMap.put("/do_login", "anon");
        // 其他未配置的所有路徑都需要通過驗證,否則跳轉到登入頁
        filterMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

上面Filter的配置順序不能隨便打亂,過濾器是按照我們配置的順序來執行的。範圍大的過濾器要放在後面,/**這條如果放在前面,那麼一來就匹配上了,就不會繼續再往後走了。這裡的對上面用到的兩個過濾器做一下簡單說明,篇幅控制其他過濾器請參閱相關文件:

* authc:配置的url都必須認證通過才可以訪問,它是Shiro內建的一個過濾器
* 對應的實現類 @see org.apache.shiro.web.filter.authc.FormAuthenticationFilter

* anon:也是Shiro內建的,它對應的過濾器裡面是空的,什麼都沒做,可以理解為不攔截
* 對應的實現類 @see org.apache.shiro.web.filter.authc.AnonymousFilter

實現兩個方法

在上一步的安全管理器配置中,我們通過形參注入了一個UserAuthorizingRealm物件,這個就是認證和授權相關的流程,需要我們自己實現。繼承AuthorizingRealm之後,我們需要實現兩個抽象方法,一個是認證,一個是授權,這兩個方法長得很像,別弄混淆了。

doGetAuthenticationInfo():認證。相當於登入,只有通過登入了,才能進行後面授權的操作。一些只需要登入許可權的操作,在登入成功後就可以訪問了,比如上一步中配置的authc過濾器就是隻需要登入許可權的。

doGetAuthorizationInfo():授權。認證過後,僅僅擁有登入許可權,更多細粒度的許可權控制,比如選單許可權,按鈕許可權,甚至方法呼叫許可權等,都可以通過授權輕鬆實現。在這個方法裡,我們可以拿到當前登入的使用者,再根據實際業務賦予使用者部分或全部許可權,當然這裡也可以賦予使用者某些角色,後面也可以根據角色鑑權。下方的演示程式碼僅新增了許可權,賦予角色可以呼叫addRoles()或者setRoles()方法,傳入角色集合。

public class UserAuthorizingRealm extends AuthorizingRealm {

    @Autowired
    private LoginService loginService;

    /**
     * 授權驗證,獲取授權資訊
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        User user = (User) principalCollection.getPrimaryPrincipal();
        List<String> perms;
        // 系統管理員擁有最高許可權
        if (User.SUPER_ADMIN == user.getId()) {
            perms = loginService.getAllPerms();
        } else {
            perms = loginService.getUserPerms(user.getId());
        }

        // 許可權Set集合
        Set<String> permsSet = new HashSet<>();
        for (String perm : perms) {
            permsSet.addAll(Arrays.asList(perm.trim().split(",")));
        }

        // 返回許可權
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(permsSet);
        return info;
    }

    /**
     * 登入驗證,獲取身份資訊
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        // 獲取使用者
        User user = loginService.getUserByUsername(token.getUsername());
        if (user == null) {
            throw new UnknownAccountException("賬號或密碼不正確");
        }
        // 判斷使用者是否被鎖定
        if (user.getStatus() == null || user.getStatus() == 1) {
            throw new LockedAccountException("賬號已被鎖定,請聯絡管理員");
        }
        // 驗證密碼
        if (!user.getPassword().equals(new String(token.getPassword()))) {
            throw new UnknownAccountException("賬號或密碼不正確");
        }
        user.setSessionId(SecurityUtils.getSubject().getSession().getId().toString());
        // 設定最後登入時間
        user.setLastLoginTime(new Date());
        // 此處可以持久化使用者的登入資訊,這裡僅做演示沒有連線資料庫
        return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
    }
}

這樣配置完成以後,就可以基於URL做粗粒度的許可權控制了,我們可以通過不同的過濾器為URL配置不同的許可權。

Shiro提供了很多內建的過濾器,我們最常用的就是第一個和第二個。如果對其效果不滿意,我們還可以自定義過濾器實現許可權控制。
文件地址:http://shiro.apache.org/web.html#default-filters

Shiro許可權管理框架(一):Shiro的基本使用


細粒度許可權控制

如果需要更細緻的許可權控制,請繼續往下新增配置,可以做到方法級別的許可權控制。其實在SpringMVC中URL也能做到方法級別控制,但是使用URL來控制方法級別的許可權配置起來簡直反人類,通常URL許可權控制通常都是泛解析,做通用的許可權配置,比如後臺管理的/admin/**這種需要登入許可權的。在實際開發中註解式的許可權控制用的最多。

AdvisorAutoProxyCreator

註解式的許可權控制需要配置兩個Bean,第一個是AdvisorAutoProxyCreator,代理生成器,需要藉助SpringAOP來掃描@RequiresRoles和@RequiresPermissions等註解,生成代理類實現功能增強,從而實現許可權控制。需要配合AuthorizationAttributeSourceAdvisor一起使用,否則許可權註解無效。

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        autoProxyCreator.setProxyTargetClass(true);
        return autoProxyCreator;
    }

AuthorizationAttributeSourceAdvisor

上面配置的DefaultAdvisorAutoProxyCreator相當於一個切面,下面這個類就相當於切點了,兩個一起才能實現註解許可權控制。

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

配置完上面兩個Bean之後我們就可以使用註解來控制許可權了,Shiro中的許可權註解有很多,我們最常用的其實就兩個,@RequiresRoles和@RequiresPermissions,前者是角色驗證,後者是許可權驗證。他們都可以傳入兩個引數,value是必須的,可以傳入一個字元陣列,表示一個或多個角色(許可權),另一個引數logical有兩個值可選,AND和OR,預設為AND,表示這組角色(許可權)是必須都有還是僅需要一個就能訪問。

舉個栗子:

    @RequestMapping("getLoginUserInfo")
    @RequiresPermissions(value = {"user:list", "user:info"}, logical = Logical.OR)
    public JsonResult getLoginUserInfo() {
        Subject subject = SecurityUtils.getSubject();
        User user = (User) subject.getPrincipal();
        return JsonResult.ok(user);
    }

以上程式碼表示getLoginUserInfo()方法需要當前登入使用者擁有user:list或者user:list許可權才能訪問。

最後放上專案程式碼,其實程式碼是很早之前的,今天才做的筆記而已。

Gitee:https://gitee.com/guitu18/ShiroDemo

GitHub:https://github.com/guitu18/ShiroDemo


本篇結束,Shiro的使用還是非常簡單的。下一篇,準備記錄一下基於Springboot和Shiro使用Redis實現叢集環境的Session共享,以實現單點登入。

相關文章