Spring boot 中使用Shiro

前言

Spring boot 是什么,網(wǎng)上的很多介紹,這里博客就不多介紹了。
如果不明白Spring boot是什么。推薦幾篇文章,你可以了解下。


  • Shiro的簡介
  • Spring boot中的使用

Shiro的簡介

Apache Shiro是Java的一個安全框架。目前,使用Apache Shiro的人越來越多,因為它相當(dāng)簡單,對比Spring Security,可能沒有Spring Security做的功能強大,但是在實際工作時可能并不需要那么復(fù)雜的東西,所以使用小而簡單的Shiro就足夠了。對于它倆到底哪個好,這個不必糾結(jié),能更簡單的解決項目問題就好了。
本教程只介紹基本的Shiro使用,不會過多分析源碼等,重在使用。

Shiro可以非常容易的開發(fā)出足夠好的應(yīng)用,其不僅可以用在JavaSE環(huán)境,也可以用在JavaEE環(huán)境。Shiro可以幫助我們完成:認(rèn)證、授權(quán)、加密、會話管理、與Web集成、緩存等。這不就是我們想要的嘛,而且Shiro的API也是非常簡單;其基本功能點如下圖所示:

image.png

Authentication:身份認(rèn)證/登錄,驗證用戶是不是擁有相應(yīng)的身份;
Authorization:授權(quán),即權(quán)限驗證,驗證某個已認(rèn)證的用戶是否擁有某個權(quán)限;即判斷用戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色。或者細(xì)粒度的驗證某個用戶對某個資源是否具有某個權(quán)限;
Session Manager:會話管理,即用戶登錄后就是一次會話,在沒有退出之前,它的所有信息都在會話中;會話可以是普通JavaSE環(huán)境的,也可以是如Web環(huán)境的;
Cryptography:加密,保護數(shù)據(jù)的安全性,如密碼加密存儲到數(shù)據(jù)庫,而不是明文存儲;
Web Support:Web支持,可以非常容易的集成到Web環(huán)境;
Caching:緩存,比如用戶登錄后,其用戶信息、擁有的角色/權(quán)限不必每次去查,這樣可以提高效率;
Concurrency:shiro支持多線程應(yīng)用的并發(fā)驗證,即如在一個線程中開啟另一個線程,能把權(quán)限自動傳播過去;
Testing:提供測試支持;
Run As:允許一個用戶假裝為另一個用戶(如果他們允許)的身份進行訪問;
Remember Me:記住我,這個是非常常見的功能,即一次登錄后,下次再來的話不用登錄了。

記住一點,Shiro不會去維護用戶、維護權(quán)限;這些需要我們自己去設(shè)計/提供;然后通過相應(yīng)的接口注入給Shiro即可。

接下來我們分別從外部和內(nèi)部來看看Shiro的架構(gòu),對于一個好的框架,從外部來看應(yīng)該具有非常簡單易于使用的API,且API契約明確;從內(nèi)部來看的話,其應(yīng)該有一個可擴展的架構(gòu),即非常容易插入用戶自定義實現(xiàn),因為任何框架都不能滿足所有需求。

image.png

可以看到:應(yīng)用代碼直接交互的對象是Subject,也就是說Shiro的對外API核心就是Subject;其每個API的含義:
Subject:主體,代表了當(dāng)前“用戶”,這個用戶不一定是一個具體的人,與當(dāng)前應(yīng)用交互的任何東西都是Subject,如網(wǎng)絡(luò)爬蟲,機器人等;即一個抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會委托給SecurityManager;可以把Subject認(rèn)為是一個門面;SecurityManager才是實際的執(zhí)行者;
SecurityManager:安全管理器;即所有與安全有關(guān)的操作都會與SecurityManager交互;且它管理著所有Subject;可以看出它是Shiro的核心,它負(fù)責(zé)與后邊介紹的其他組件進行交互,如果學(xué)習(xí)過SpringMVC,你可以把它看成DispatcherServlet前端控制器;
Realm:域,Shiro從從Realm獲取安全數(shù)據(jù)(如用戶、角色、權(quán)限),就是說SecurityManager要驗證用戶身份,那么它需要從Realm獲取相應(yīng)的用戶進行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應(yīng)的角色/權(quán)限進行驗證用戶是否能進行操作;可以把Realm看成DataSource,即安全數(shù)據(jù)源。

也就是說對于我們而言,最簡單的一個Shiro應(yīng)用:
1、應(yīng)用代碼通過Subject來進行認(rèn)證和授權(quán),而Subject又委托給SecurityManager;
2、我們需要給Shiro的SecurityManager注入Realm,從而讓SecurityManager能得到合法的用戶及其權(quán)限進行判斷。

從以上也可以看出,Shiro不提供維護用戶/權(quán)限,而是通過Realm讓開發(fā)人員自己注入。

接下來我們來從Shiro內(nèi)部來看下Shiro的架構(gòu),如下圖所示:

image.png

Subject:主體,可以看到主體可以是任何可以與應(yīng)用交互的“用戶”;
SecurityManager:相當(dāng)于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心臟;所有具體的交互都通過SecurityManager進行控制;它管理著所有Subject、且負(fù)責(zé)進行認(rèn)證和授權(quán)、及會話、緩存的管理。
Authenticator:認(rèn)證器,負(fù)責(zé)主體認(rèn)證的,這是一個擴展點,如果用戶覺得Shiro默認(rèn)的不好,可以自定義實現(xiàn);其需要認(rèn)證策略(Authentication Strategy),即什么情況下算用戶認(rèn)證通過了;
Authrizer:授權(quán)器,或者訪問控制器,用來決定主體是否有權(quán)限進行相應(yīng)的操作;即控制著用戶能訪問應(yīng)用中的哪些功能;
Realm:可以有1個或多個Realm,可以認(rèn)為是安全實體數(shù)據(jù)源,即用于獲取安全實體的;可以是JDBC實現(xiàn),也可以是LDAP實現(xiàn),或者內(nèi)存實現(xiàn)等等;由用戶提供;注意:Shiro不知道你的用戶/權(quán)限存儲在哪及以何種格式存儲;所以我們一般在應(yīng)用中都需要實現(xiàn)自己的Realm;
SessionManager:如果寫過Servlet就應(yīng)該知道Session的概念,Session呢需要有人去管理它的生命周期,這個組件就是SessionManager;而Shiro并不僅僅可以用在Web環(huán)境,也可以用在如普通的JavaSE環(huán)境、EJB等環(huán)境;所有呢,Shiro就抽象了一個自己的Session來管理主體與應(yīng)用之間交互的數(shù)據(jù);這樣的話,比如我們在Web環(huán)境用,剛開始是一臺Web服務(wù)器;接著又上了臺EJB服務(wù)器;這時想把兩臺服務(wù)器的會話數(shù)據(jù)放到一個地方,這個時候就可以實現(xiàn)自己的分布式會話(如把數(shù)據(jù)放到Memcached服務(wù)器);
SessionDAO:DAO大家都用過,數(shù)據(jù)訪問對象,用于會話的CRUD,比如我們想把Session保存到數(shù)據(jù)庫,那么可以實現(xiàn)自己的SessionDAO,通過如JDBC寫到數(shù)據(jù)庫;比如想把Session放到Memcached中,可以實現(xiàn)自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache進行緩存,以提高性能;
CacheManager:緩存控制器,來管理如用戶、角色、權(quán)限等的緩存的;因為這些數(shù)據(jù)基本上很少去改變,放到緩存中后可以提高訪問的性能
Cryptography:密碼模塊,Shiro提高了一些常見的加密組件用于如密碼加密/解密的。

詳細(xì)可查看官網(wǎng)

Spring boot中的使用

demo地址

demo實現(xiàn)記住我,驗證碼登錄,權(quán)限自定義(or),緩存功能等。
你們看這篇博客可以先把項目下載下來,里面的注釋我都寫的比較完整。

核心依賴

<!-- shiro 權(quán)限控制 -->
<dependency>
     <groupId>org.apache.shiro</groupId>
     <artifactId>shiro-spring</artifactId>
     <version>1.2.2</version>
</dependency>        

緩存依賴

<!-- shiro ehcache (shiro緩存)-->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-ehcache -->
<dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.2.2</version>
</dependency>

這里使用了驗證碼框架是google的kaptcha

<!--google的驗證碼框架-->
<dependency>
        <groupId>com.google.code.kaptcha</groupId>
        <artifactId>kaptcha</artifactId>
        <version>2.3</version>
</dependency>

測試用的模板引擎是thymeleaf

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

1.新建ShiroConfiguration類
Shiro有幾個核心的類,第一個在于ShiroFilterFactoryBean,第二個就是SecurityManager,代碼如下:

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 * packageName  : com.xuezhijian.shiro
 * User         : zj
 * Date         : 17/2/13
 * Time         : 下午11:14
 * Description  : Apache Shiro 核心通過 Filter 來實現(xiàn),就好像SpringMvc 通過DispachServlet 來主控制一樣。
 * 既然是使用 Filter 一般也就能猜到,是通過URL規(guī)則來進行過濾和權(quán)限校驗,所以我們需要定義一系列關(guān)于URL的規(guī)則和訪問權(quán)限。
 */

@Configuration
@Order(1)
public class ShiroConfiguration {

    /**
     * ShiroFilterFactoryBean 處理攔截資源文件問題。
     * 注意:單獨一個ShiroFilterFactoryBean配置是或報錯的,以為在
     * 初始化ShiroFilterFactoryBean的時候需要注入:SecurityManager Filter Chain定義說明
     * 1、一個URL可以配置多個Filter,使用逗號分隔 2、當(dāng)設(shè)置多個過濾器時,全部驗證通過,才視為通過
     * 3、部分過濾器可指定參數(shù),如perms,roles
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必須設(shè)置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //驗證碼過濾器
        Map<String, Filter> filtersMap = shiroFilterFactoryBean.getFilters();
        KaptchaFilter kaptchaFilter = new KaptchaFilter();
        filtersMap.put("kaptchaFilter", kaptchaFilter);
        //實現(xiàn)自己規(guī)則roles,這是為了實現(xiàn)or的效果
        //RoleFilter roleFilter = new RoleFilter();
        //filtersMap.put("roles", roleFilter);
        shiroFilterFactoryBean.setFilters(filtersMap);


        // 攔截器.
        //rest:比如/admins/user/**=rest[user],根據(jù)請求的方法,相當(dāng)于/admins/user/**=perms[user:method] ,其中method為post,get,delete等。
        //port:比如/admins/user/**=port[8081],當(dāng)請求的url的端口不是8081是跳轉(zhuǎn)到schemal://serverName:8081?queryString,其中schmal是協(xié)議http或https等,serverName是你訪問的host,8081是url配置里port的端口,queryString是你訪問的url里的?后面的參數(shù)。
        //perms:比如/admins/user/**=perms[user:add:*],perms參數(shù)可以寫多個,多個時必須加上引號,并且參數(shù)之間用逗號分割,比如/admins/user/**=perms["user:add:*,user:modify:*"],當(dāng)有多個參數(shù)時必須每個參數(shù)都通過才通過,想當(dāng)于isPermitedAll()方法。
        //roles:比如/admins/user/**=roles[admin],參數(shù)可以寫多個,多個時必須加上引號,并且參數(shù)之間用逗號分割,當(dāng)有多個參數(shù)時,比如/admins/user/**=roles["admin,guest"],每個參數(shù)通過才算通過,相當(dāng)于hasAllRoles()方法。//要實現(xiàn)or的效果看http://zgzty.blog.163.com/blog/static/83831226201302983358670/
        //anon:比如/admins/**=anon 沒有參數(shù),表示可以匿名使用。
        //authc:比如/admins/user/**=authc表示需要認(rèn)證才能使用,沒有參數(shù)
        //authcBasic:比如/admins/user/**=authcBasic沒有參數(shù)表示httpBasic認(rèn)證
        //ssl:比如/admins/user/**=ssl沒有參數(shù),表示安全的url請求,協(xié)議為https
        //user:比如/admins/user/**=user沒有參數(shù)表示必須存在用戶,當(dāng)?shù)侨氩僮鲿r不做檢查

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置退出過濾器,其中的具體的退出代碼Shiro已經(jīng)替我們實現(xiàn)了

        filterChainDefinitionMap.put("/logout", "logout");
        //配置記住我或認(rèn)證通過可以訪問的地址
        filterChainDefinitionMap.put("/index", "user");
        filterChainDefinitionMap.put("/", "user");
        filterChainDefinitionMap.put("/login", "kaptchaFilter");

        // <!-- 過濾鏈定義,從上向下順序執(zhí)行,一般將 /**放在最為下邊 -->:這是一個坑呢,一不小心代碼就不好使了;

        //這段是配合 actuator框架使用的,配置相應(yīng)的角色才能訪問
//        filterChainDefinitionMap.put("/health", "roles[aix]");//服務(wù)器健康狀況頁面
//        filterChainDefinitionMap.put("/info", "roles[aix]");//服務(wù)器信息頁面
//        filterChainDefinitionMap.put("/env", "roles[aix]");//應(yīng)用程序的環(huán)境變量
//        filterChainDefinitionMap.put("/metrics", "roles[aix]");
//        filterChainDefinitionMap.put("/configprops", "roles[aix]");

        //開放的靜態(tài)資源
        filterChainDefinitionMap.put("/favicon.ico", "anon");//網(wǎng)站圖標(biāo)
        filterChainDefinitionMap.put("/AdminLTE-2.3.7/**", "anon");//配置static文件下資源能被訪問的,這是個例子

        filterChainDefinitionMap.put("/kaptcha.jpg", "anon");//圖片驗證碼(kaptcha框架)

        filterChainDefinitionMap.put("/**", "authc");

        // 如果不設(shè)置默認(rèn)會自動尋找Web工程根目錄下的"/login.jsp"頁面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登錄成功后要跳轉(zhuǎn)的鏈接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 未授權(quán)界面
        shiroFilterFactoryBean.setUnauthorizedUrl("/errorView/403_error.html");//不生效(詳情原因看MyExceptionResolver)
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 設(shè)置realm.
        securityManager.setRealm(myShiroRealm());
        //注入緩存管理器;
        //注意:開發(fā)時請先關(guān)閉,如不關(guān)閉熱啟動會報錯
        //securityManager.setCacheManager(ehCacheManager());//這個如果執(zhí)行多次,也是同樣的一個對象;
        //注入記住我管理器;
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

    /**
     * 身份認(rèn)證realm; (這個需要自己寫,賬號密碼校驗;權(quán)限等)
     */
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }

    /**
     * 憑證匹配器 (由于我們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了
     * 所以我們需要修改下doGetAuthenticationInfo中的代碼; @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:這里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2);// 散列的次數(shù),比如散列兩次,相當(dāng)于md5(md5(""));
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);//表示是否存儲散列后的密碼為16進制,需要和生成密碼時的一樣,默認(rèn)是base64;
        return hashedCredentialsMatcher;
    }

    /**
     * 開啟shiro aop注解支持. 使用代理方式;所以需要開啟代碼支持;
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    /**
     * shiro緩存管理器;
     * 需要注入對應(yīng)的其它的實體類中:
     * 1、安全管理器:securityManager
     * 可見securityManager是整個shiro的核心;
     *
     * @return
     */
    @Bean
    public EhCacheManager ehCacheManager(){
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");
        return cacheManager;
    }
    /**
     * cookie對象;
     * @return
     * */
    @Bean
    public SimpleCookie rememberMeCookie(){
        //System.out.println("ShiroConfiguration.rememberMeCookie()");
        //這個參數(shù)是cookie的名稱,對應(yīng)前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //<!-- 記住我cookie生效時間30天 ,單位秒;-->
        simpleCookie.setMaxAge(259200);
        return simpleCookie;
    }
    /**
     * cookie管理對象;
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        //System.out.println("ShiroConfiguration.rememberMeManager()");
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return cookieRememberMeManager;
    }
}

以上代碼的解釋:
shirFilter方法是注入ShiroFilterFactoryBean相關(guān)屬性:
設(shè)置SecurityManager安全管理器:(下面會有詳細(xì)點的介紹)

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必須設(shè)置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);

設(shè)置攔截規(guī)則:這個map可配置key-value去完成相應(yīng)的攔截操作,例如配置登錄,登出,哪些靜態(tài)資源需要權(quán)限管理或者排除在權(quán)限管理之外。還可以自定義過濾器.demo中有驗證碼過濾器例子。

Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();

通過ShiroFilterFactoryBean注入:

shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

代碼中都有更詳細(xì)的解釋說明。

  1. SecurityManager的注入,結(jié)合自定義的MyShiroRealm(身份認(rèn)證),查詢數(shù)據(jù)庫的用戶信息和權(quán)限,對用戶密碼進行加鹽加密。
@Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 設(shè)置realm.
        securityManager.setRealm(myShiroRealm());
        //注入緩存管理器;
        //注意:開發(fā)時請先關(guān)閉,如不關(guān)閉熱啟動會報錯
        //securityManager.setCacheManager(ehCacheManager());//這個如果執(zhí)行多次,也是同樣的一個對象;
        //注入記住我管理器;
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }

MyShiroRealm的代碼:

package com.xuezhijian.shiro;

import com.xuezhijian.dao.entity.ManagerInfo;
import com.xuezhijian.dao.entity.SysPermission;
import com.xuezhijian.dao.entity.SysRole;
import com.xuezhijian.service.ManagerInfoService;
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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created with IntelliJ IDEA.
 * packageName  : com.xuezhijian.shiro
 * User         : zj
 * Date         : 17/2/13
 * Time         : 下午11:28
 * Description  : 身份校驗核心類
 */

public class MyShiroRealm extends AuthorizingRealm {

    @Autowired
    ManagerInfoService managerInfoService;


    /**
     * 認(rèn)證信息.(身份驗證)
     * Authentication 是用來驗證用戶身份
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {

        //System.out.println("MyShiroRealm.doGetAuthenticationInfo()");

        //獲取用戶的輸入的賬號.
        String username = (String)token.getPrincipal();
        //System.out.println("用戶的賬號:"+username);

        //通過username從數(shù)據(jù)庫中查找 ManagerInfo對象
        //實際項目中,這里可以根據(jù)實際情況做緩存,如果不做,Shiro自己也是有時間間隔機制,2分鐘內(nèi)不會重復(fù)執(zhí)行該方法
        ManagerInfo managerInfo = managerInfoService.findByUsername(username);

//        System.out.println("----->>managerInfo="+managerInfo.toString());
        if(managerInfo == null){
            return null;
        }

        //交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配,如果覺得人家的不好可以自定義實現(xiàn)
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                managerInfo, //用戶名
                managerInfo.getPassword(), //密碼
                ByteSource.Util.bytes(managerInfo.getCredentialsSalt()),//salt=username+salt
                getName()  //realm name
        );

        //明文: 若存在,將此用戶存放到登錄認(rèn)證info中,無需自己做密碼對比,Shiro會為我們進行密碼對比校驗
//        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
//                managerInfo, //用戶名
//                managerInfo.getPassword(), //密碼
//                getName()  //realm name
//        );
        return authenticationInfo;
    }

    /**
     * 此方法調(diào)用  hasRole,hasPermission的時候才會進行回調(diào).
     *
     * 權(quán)限信息.(授權(quán)):
     * 1、如果用戶正常退出,緩存自動清空;
     * 2、如果用戶非正常退出,緩存自動清空;
     * 3、如果我們修改了用戶的權(quán)限,而用戶不退出系統(tǒng),修改的權(quán)限無法立即生效。
     * (需要手動編程進行實現(xiàn);放在service進行調(diào)用)
     * 在權(quán)限修改后調(diào)用realm中的方法,realm已經(jīng)由spring管理,所以從spring中獲取realm實例,
     * 調(diào)用clearCached方法;
     * :Authorization 是授權(quán)訪問控制,用于對用戶進行的操作授權(quán),證明該用戶是否允許進行當(dāng)前操作,如訪問某個鏈接,某個資源文件等。
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        /*
        * 當(dāng)沒有使用緩存的時候,不斷刷新頁面的話,這個代碼會不斷執(zhí)行,
        * 當(dāng)其實沒有必要每次都重新設(shè)置權(quán)限信息,所以我們需要放到緩存中進行管理;
        * 當(dāng)放到緩存中時,這樣的話,doGetAuthorizationInfo就只會執(zhí)行一次了,
        * 緩存過期之后會再次執(zhí)行。
        */
        //System.out.println("權(quán)限配置-->MyShiroRealm.doGetAuthorizationInfo()");

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        ManagerInfo managerInfo  = (ManagerInfo)principals.getPrimaryPrincipal();

        //實際項目中,這里可以根據(jù)實際情況做緩存,如果不做,Shiro自己也是有時間間隔機制,2分鐘內(nèi)不會重復(fù)執(zhí)行該方法
//     UserInfo userInfo = userInfoService.findByUsername(username)

        //設(shè)置相應(yīng)角色的權(quán)限信息
        for(SysRole role:managerInfo.getRoles()){
            //設(shè)置角色
            authorizationInfo.addRole(role.getRole());
            for(SysPermission p:role.getPermissions()){
                //設(shè)置權(quán)限
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }

        return authorizationInfo;
    }

}

3.如果想實現(xiàn)驗證碼功能的話
可以繼承FormAuthenticationFilter授權(quán)過濾器拓展自已的驗證碼功能

package com.xuezhijian.shiro;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

/**
 * Created with IntelliJ IDEA.
 * packageName  : com.xuezhijian.shiro
 * User         : zj
 * Date         : 17/2/13
 * Time         : 下午11:30
 * Description  : 驗證碼過濾器此過濾器已經(jīng)在shiro中配置,這里不需要再次配置攔截路徑
 */
public class KaptchaFilter extends FormAuthenticationFilter {

    public static final String DEFAULT_CAPTCHA_PARAM = "captcha";

    private String captchaParam = DEFAULT_CAPTCHA_PARAM;

    //登錄驗證
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response)
            throws Exception {

        CaptchaUsernamePasswordToken token = createToken(request, response);
        String username = token.getUsername();
        try {
            //System.out.println("KaptchaFilter.executeLogin");
            /*圖形驗證碼驗證*/
            doCaptchaValidate((HttpServletRequest) request, token);
            Subject subject = getSubject(request, response);
            subject.login(token);//正常驗證

            //到這里就算驗證成功了,把用戶信息放到session中
            ((HttpServletRequest) request).getSession().setAttribute("name",username);

            return onLoginSuccess(token, subject, request, response);
        }catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
    }

    // 驗證碼校驗
    protected void doCaptchaValidate(HttpServletRequest request, CaptchaUsernamePasswordToken token) {
        //System.out.println("KaptchaFilter.doCaptchaValidate");

        //session中的圖形碼字符串
        String captcha = (String) request.getSession().getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
        //System.out.println("session中的圖形碼字符串:"+captcha);

        //比對
        if (captcha == null || !captcha.equalsIgnoreCase(token.getCaptcha())) {
            throw new IncorrectCaptchaException();
        }
    }

    @Override
    protected CaptchaUsernamePasswordToken createToken(ServletRequest request, ServletResponse response) {

        String username = getUsername(request);
        String password = getPassword(request);
        String captcha = getCaptcha(request);
        boolean rememberMe = isRememberMe(request);
        String host = getHost(request);

        return new CaptchaUsernamePasswordToken(username, password.toCharArray(), rememberMe, host, captcha);
    }



    public String getCaptchaParam() {
        return captchaParam;
    }

    public void setCaptchaParam(String captchaParam) {
        this.captchaParam = captchaParam;
    }

    protected String getCaptcha(ServletRequest request) {
        return WebUtils.getCleanParam(request, getCaptchaParam());
    }

    //保存異常對象到request
    @Override
    protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
        request.setAttribute(getFailureKeyAttribute(), ae);
    }

}

4.shiro緩存管理器,因為我們不可能每次都要去查庫查詢用戶的權(quán)限信息,應(yīng)該是查詢一次后放在緩存中,下次去緩存中去查找,設(shè)定緩存時效,ehcache-shiro.xml只是簡單的配置下緩存的策略,具體可在項目中查看,還有這里有實現(xiàn)rememberMe功能,核心代碼如下,

/**
     * shiro緩存管理器;
     * 需要注入對應(yīng)的其它的實體類中:
     * 1、安全管理器:securityManager
     * 可見securityManager是整個shiro的核心;
     *
     * @return
     */
    @Bean
    public EhCacheManager ehCacheManager(){
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");
        return cacheManager;
    }
    /**
     * cookie對象;
     * @return
     * */
    @Bean
    public SimpleCookie rememberMeCookie(){
        //System.out.println("ShiroConfiguration.rememberMeCookie()");
        //這個參數(shù)是cookie的名稱,對應(yīng)前端的checkbox的name = rememberMe
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        //<!-- 記住我cookie生效時間30天 ,單位秒;-->
        simpleCookie.setMaxAge(259200);
        return simpleCookie;
    }
    /**
     * cookie管理對象;
     * @return
     */
    @Bean
    public CookieRememberMeManager rememberMeManager(){
        //System.out.println("ShiroConfiguration.rememberMeManager()");
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return cookieRememberMeManager;
    }

demo項目結(jié)構(gòu):

image.png
image.png

你可以把項目下載下來,修改下數(shù)據(jù)庫(項目中帶sql腳本,簡單的權(quán)限,因為是demo就不做復(fù)雜的權(quán)限的表設(shè)計了。)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Shiro(代碼) 1.1 簡介 Apache Shiro是Java的一個安全框架。目前,使用Apache Shi...
    ZZS_簡閱讀 493評論 0 0
  • 一、架構(gòu) 要學(xué)習(xí)如何使用Shiro必須先從它的架構(gòu)談起,作為一款安全框架Shiro的設(shè)計相當(dāng)精妙。Shiro的應(yīng)用...
    ITsupuerlady閱讀 3,546評論 4 32
  • Apache Shiro Apache Shiro 是一個強大而靈活的開源安全框架,它干凈利落地處理身份認(rèn)證,授權(quán)...
    羅志贇閱讀 3,249評論 1 49
  • 文章轉(zhuǎn)載自:http://blog.csdn.net/w1196726224/article/details/53...
    wangzaiplus閱讀 3,412評論 0 3
  • 1.簡介 Apache Shiro是Java的一個安全框架。功能強大,使用簡單的Java安全框架,它為開發(fā)人員提供...
    H_Man閱讀 3,181評論 4 47