Java中Shiro安全框架的權(quán)限管理
前言
Apache Shiro是Java的一個(gè)安全框架。
目前,使用Apache Shiro的人越來越多,相比Spring Security而言相當(dāng)簡(jiǎn)單, 可能沒有Spring Security做的功能強(qiáng)大,但是在實(shí)際工作時(shí)可能并不需要那么復(fù)雜的東西, 所以使用小而簡(jiǎn)單的Shiro就足夠了。
對(duì)于它倆到底哪個(gè)好,這個(gè)不必糾結(jié),能更簡(jiǎn)單的解決項(xiàng)目問題就好了。
本文只介紹基本的Shiro使用,不會(huì)過多分析源碼等,重在使用。
Shiro架構(gòu)
Shiro可以非常容易的開發(fā)出足夠好的應(yīng)用,其不僅可以用在JavaSE環(huán)境,也可以用在JavaEE環(huán)境。
Shiro可以幫助我們完成:認(rèn)證、授權(quán)、加密、會(huì)話管理、與Web集成、緩存等。
Shiro的API非常簡(jiǎn)單,其基本功能點(diǎn)如下圖所示:

- Authentication:身份認(rèn)證/登錄,驗(yàn)證用戶是不是擁有相應(yīng)的身份;
- Authorization:授權(quán),即權(quán)限驗(yàn)證,驗(yàn)證某個(gè)已認(rèn)證的用戶是否擁有某個(gè)權(quán)限;即判斷用戶是否能做事情,常見的如:驗(yàn)證某個(gè)用戶是否擁有某個(gè)角色。或者細(xì)粒度的驗(yàn)證某個(gè)用戶對(duì)某個(gè)資源是否具有某個(gè)權(quán)限;
- Session Manager:會(huì)話管理,即用戶登錄后就是一次會(huì)話,在沒有退出之前,它的所有信息都在會(huì)話中;會(huì)話可以是普通JavaSE環(huán)境的,也可以是如Web環(huán)境的;
- Cryptography:加密,保護(hù)數(shù)據(jù)的安全性,如密碼加密存儲(chǔ)到數(shù)據(jù)庫,而不是明文存儲(chǔ);
- Web Support:Web支持,可以非常容易的集成到Web環(huán)境;
- Caching:緩存,比如用戶登錄后,其用戶信息、擁有的角色/權(quán)限不必每次去查,這樣可以提高效率;
- Concurrency:shiro支持多線程應(yīng)用的并發(fā)驗(yàn)證,即如在一個(gè)線程中開啟另一個(gè)線程,能把權(quán)限自動(dòng)傳播過去;
- Testing:提供測(cè)試支持;
- Run As:允許一個(gè)用戶假裝為另一個(gè)用戶(如果他們?cè)试S)的身份進(jìn)行訪問;
- Remember Me:記住我,這個(gè)是非常常見的功能,即一次登錄后,下次再來的話不用登錄了。
記住一點(diǎn),Shiro不會(huì)去維護(hù)用戶、維護(hù)權(quán)限,這些需要我們自己去設(shè)計(jì)/提供,然后通過相應(yīng)的接口注入給Shiro即可。
接下來我們分別從外部和內(nèi)部來看看Shiro的架構(gòu),對(duì)于一個(gè)好的框架,從外部來看應(yīng)該具有非常簡(jiǎn)單易于使用的API, 且API契約明確;從內(nèi)部來看的話,其應(yīng)該有一個(gè)可擴(kuò)展的架構(gòu),即非常容易插入用戶自定義實(shí)現(xiàn),因?yàn)槿魏慰蚣芏疾荒軡M足所有需求。

可以看到:應(yīng)用代碼直接交互的對(duì)象是Subject,也就是說Shiro的對(duì)外API核心就是 Subject。
- Subject:主體,代表了當(dāng)前”用戶”,這個(gè)用戶不一定是一個(gè)具體的人,與當(dāng)前應(yīng)用交互的任何東西都是Subject,如網(wǎng)絡(luò)爬蟲,機(jī)器人等;即一個(gè)抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會(huì)委托給SecurityManager;可以把Subject認(rèn)為是一個(gè)門面;SecurityManager才是實(shí)際的執(zhí)行者;
- SecurityManager:安全管理器;即所有與安全有關(guān)的操作都會(huì)與SecurityManager交互;且它管理著所有Subject;可以看出它是Shiro的核心,它負(fù)責(zé)與后邊介紹的其他組件進(jìn)行交互,如果學(xué)習(xí)過SpringMVC,你可以把它看成DispatcherServlet前端控制器;
- Realm:域,Shiro從從Realm獲取安全數(shù)據(jù)(如用戶、角色、權(quán)限),就是說SecurityManager要驗(yàn)證用戶身份,那么它需要從Realm獲取相應(yīng)的用戶進(jìn)行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應(yīng)的角色/權(quán)限進(jìn)行驗(yàn)證用戶是否能進(jìn)行操作;可以把Realm看成DataSource,即安全數(shù)據(jù)源。
也就是說對(duì)于我們而言,最簡(jiǎn)單的一個(gè)Shiro應(yīng)用:
1、應(yīng)用代碼通過Subject 來進(jìn)行認(rèn)證和授權(quán),而 Subject 又委托給 SecurityManager;
2、我們需要給Shiro的 SecurityManager 注入 Realm,從而讓 SecurityManager 能得到合法的用戶及其權(quán)限進(jìn)行判斷。
從以上也可以看出,Shiro不提供維護(hù)用戶/權(quán)限,而是通過Realm讓開發(fā)人員自己注入。
接下來我們來從Shiro內(nèi)部來看下Shiro的架構(gòu),如下圖所示:

- Subject:主體,可以看到主體可以是任何可以與應(yīng)用交互的”用戶”;
- SecurityManager:相當(dāng)于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心臟;所有具體的交互都通過SecurityManager進(jìn)行控制;它管理著所有Subject、且負(fù)責(zé)進(jìn)行認(rèn)證和授權(quán)、及會(huì)話、緩存的管理。
- Authenticator:認(rèn)證器,負(fù)責(zé)主體認(rèn)證的,這是一個(gè)擴(kuò)展點(diǎn),如果用戶覺得Shiro默認(rèn)的不好,可以自定義實(shí)現(xiàn);其需要認(rèn)證策略(Authentication Strategy),即什么情況下算用戶認(rèn)證通過了;
- Authorizer:授權(quán)器,或者訪問控制器,用來決定主體是否有權(quán)限進(jìn)行相應(yīng)的操作;即控制著用戶能訪問應(yīng)用中的哪些功能;
- Realm:可以有1個(gè)或多個(gè)Realm,可以認(rèn)為是安全實(shí)體數(shù)據(jù)源,即用于獲取安全實(shí)體的;可以是JDBC實(shí)現(xiàn),也可以是LDAP實(shí)現(xiàn),或者內(nèi)存實(shí)現(xiàn)等等;由用戶提供;注意:Shiro不知道你的用戶/權(quán)限存儲(chǔ)在哪及以何種格式存儲(chǔ);所以我們一般在應(yīng)用中都需要實(shí)現(xiàn)自己的Realm;
- SessionManager:如果寫過Servlet就應(yīng)該知道Session的概念,Session呢需要有人去管理它的生命周期,這個(gè)組件就是SessionManager;而Shiro并不僅僅可以用在Web環(huán)境,也可以用在如普通的JavaSE環(huán)境、EJB等環(huán)境;所有呢,Shiro就抽象了一個(gè)自己的Session來管理主體與應(yīng)用之間交互的數(shù)據(jù);這樣的話,比如我們?cè)赪eb環(huán)境用,剛開始是一臺(tái)Web服務(wù)器;接著又上了臺(tái)EJB服務(wù)器;這時(shí)想把兩臺(tái)服務(wù)器的會(huì)話數(shù)據(jù)放到一個(gè)地方,這個(gè)時(shí)候就可以實(shí)現(xiàn)自己的分布式會(huì)話(如把數(shù)據(jù)放到Memcached服務(wù)器);
- SessionDAO:DAO大家都用過,數(shù)據(jù)訪問對(duì)象,用于會(huì)話的CRUD,比如我們想把Session保存到數(shù)據(jù)庫,那么可以實(shí)現(xiàn)自己的SessionDAO,通過如JDBC寫到數(shù)據(jù)庫;比如想把Session放到Memcached中,可以實(shí)現(xiàn)自己的Memcached SessionDAO,另外SessionDAO中可以使用Cache進(jìn)行緩存,以提高性能;
- CacheManager:緩存控制器,來管理如用戶、角色、權(quán)限等的緩存的;因?yàn)檫@些數(shù)據(jù)基本上很少去改變,放到緩存中后可以提高訪問的性能
- Cryptography:密碼模塊,Shiro提高了一些常見的加密組件用于如密碼加密/解密的。
SpringBoot 集成
實(shí)現(xiàn)一個(gè)最常見的驗(yàn)證碼登錄、記住我、權(quán)限自定義(or),緩存功能的SpringBoot應(yīng)用,模板使用Thymeleaf3
Maven依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- thymeleaf模板中shiro標(biāo)簽-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<!-- shiro 權(quán)限控制 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- shiro ehcache (shiro緩存)-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<!--驗(yàn)證碼框架-->
<dependency>
<groupId>com.github.axet</groupId>
<artifactId>kaptcha</artifactId>
<version>0.0.9</version>
</dependency>新建一個(gè)配置類ShiroConfig.java,內(nèi)容如下:
/**
* Description : Apache Shiro 核心通過 Filter 來實(shí)現(xiàn),就好像SpringMvc 通過DispachServlet 來主控制一樣。
* 既然是使用 Filter 一般也就能猜到,是通過URL規(guī)則來進(jìn)行過濾和權(quán)限校驗(yàn),所以我們需要定義一系列關(guān)于URL的規(guī)則和訪問權(quán)限。
*/
@Configuration
@Order(1)
public class ShiroConfig {
//配置kaptcha圖片驗(yàn)證碼框架提供的Servlet,,這是個(gè)坑,很多人忘記注冊(cè)(注意)
@Bean
public ServletRegistrationBean kaptchaServlet() {
ServletRegistrationBean servlet = new ServletRegistrationBean(new KaptchaServlet(), "/kaptcha.jpg");
servlet.addInitParameter(Constants.KAPTCHA_SESSION_CONFIG_KEY, Constants.KAPTCHA_SESSION_KEY);//session key
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "50");//字體大小
servlet.addInitParameter(Constants.KAPTCHA_BORDER, "no");
servlet.addInitParameter(Constants.KAPTCHA_BORDER_COLOR, "105,179,90");
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_SIZE, "45");
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4");
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_NAMES, "宋體,楷體,微軟雅黑");
servlet.addInitParameter(Constants.KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue");
servlet.addInitParameter(Constants.KAPTCHA_IMAGE_WIDTH, "125");
servlet.addInitParameter(Constants.KAPTCHA_IMAGE_HEIGHT, "60");
//可以設(shè)置很多屬性,具體看com.google.code.kaptcha.Constants
// kaptcha.border 是否有邊框 默認(rèn)為true 我們可以自己設(shè)置yes,no
// kaptcha.border.color 邊框顏色 默認(rèn)為Color.BLACK
// kaptcha.border.thickness 邊框粗細(xì)度 默認(rèn)為1
// kaptcha.producer.impl 驗(yàn)證碼生成器 默認(rèn)為DefaultKaptcha
// kaptcha.textproducer.impl 驗(yàn)證碼文本生成器 默認(rèn)為DefaultTextCreator
// kaptcha.textproducer.char.string 驗(yàn)證碼文本字符內(nèi)容范圍 默認(rèn)為abcde2345678gfynmnpwx
// kaptcha.textproducer.char.length 驗(yàn)證碼文本字符長(zhǎng)度 默認(rèn)為5
// kaptcha.textproducer.font.names 驗(yàn)證碼文本字體樣式 默認(rèn)為new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
// kaptcha.textproducer.font.size 驗(yàn)證碼文本字符大小 默認(rèn)為40
// kaptcha.textproducer.font.color 驗(yàn)證碼文本字符顏色 默認(rèn)為Color.BLACK
// kaptcha.textproducer.char.space 驗(yàn)證碼文本字符間距 默認(rèn)為2
// kaptcha.noise.impl 驗(yàn)證碼噪點(diǎn)生成對(duì)象 默認(rèn)為DefaultNoise
// kaptcha.noise.color 驗(yàn)證碼噪點(diǎn)顏色 默認(rèn)為Color.BLACK
// kaptcha.obscurificator.impl 驗(yàn)證碼樣式引擎 默認(rèn)為WaterRipple
// kaptcha.word.impl 驗(yàn)證碼文本字符渲染 默認(rèn)為DefaultWordRenderer
// kaptcha.background.impl 驗(yàn)證碼背景生成器 默認(rèn)為DefaultBackground
// kaptcha.background.clear.from 驗(yàn)證碼背景顏色漸進(jìn) 默認(rèn)為Color.LIGHT_GRAY
// kaptcha.background.clear.to 驗(yàn)證碼背景顏色漸進(jìn) 默認(rèn)為Color.WHITE
// kaptcha.image.width 驗(yàn)證碼圖片寬度 默認(rèn)為200
// kaptcha.image.height 驗(yàn)證碼圖片高度 默認(rèn)為50
return servlet;
}
//注入異常處理類
@Bean
public MyExceptionResolver myExceptionResolver() {
return new MyExceptionResolver();
}
/**
* ShiroFilterFactoryBean 處理攔截資源文件問題。
* 注意:?jiǎn)为?dú)一個(gè)ShiroFilterFactoryBean配置是或報(bào)錯(cuò)的,以為在
* 初始化ShiroFilterFactoryBean的時(shí)候需要注入:SecurityManager Filter Chain定義說明
* 1、一個(gè)URL可以配置多個(gè)Filter,使用逗號(hào)分隔
* 2、當(dāng)設(shè)置多個(gè)過濾器時(shí),全部驗(yàn)證通過,才視為通過
* 3、部分過濾器可指定參數(shù),如perms,roles
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必須設(shè)置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//驗(yàn)證碼過濾器
Map<String, Filter> filtersMap = shiroFilterFactoryBean.getFilters();
KaptchaFilter kaptchaFilter = new KaptchaFilter();
filtersMap.put("kaptchaFilter", kaptchaFilter);
//實(shí)現(xiàn)自己規(guī)則roles,這是為了實(shí)現(xiàn)or的效果
//RoleFilter roleFilter = new RoleFilter();
//filtersMap.put("roles", roleFilter);
shiroFilterFactoryBean.setFilters(filtersMap);
// 攔截器
//rest:比如/admins/user/**=rest[user],根據(jù)請(qǐng)求的方法,相當(dāng)于/admins/user/**=perms[user:method] ,其中method為post,get,delete等。
//port:比如/admins/user/**=port[8081],當(dāng)請(qǐ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ù)可以寫多個(gè),多個(gè)時(shí)必須加上引號(hào),并且參數(shù)之間用逗號(hào)分割,比如/admins/user/**=perms["user:add:*,user:modify:*"],當(dāng)有多個(gè)參數(shù)時(shí)必須每個(gè)參數(shù)都通過才通過,想當(dāng)于isPermitedAll()方法。
//roles:比如/admins/user/**=roles[admin],參數(shù)可以寫多個(gè),多個(gè)時(shí)必須加上引號(hào),并且參數(shù)之間用逗號(hào)分割,當(dāng)有多個(gè)參數(shù)時(shí),比如/admins/user/**=roles["admin,guest"],每個(gè)參數(shù)通過才算通過,相當(dāng)于hasAllRoles()方法。//要實(shí)現(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請(qǐng)求,協(xié)議為https
//user:比如/admins/user/**=user沒有參數(shù)表示必須存在用戶,當(dāng)?shù)侨氩僮鲿r(shí)不做檢查
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置退出過濾器,其中的具體的退出代碼Shiro已經(jīng)替我們實(shí)現(xiàn)了
filterChainDefinitionMap.put("/logout", "logout");
//配置記住我或認(rèn)證通過可以訪問的地址
filterChainDefinitionMap.put("/index", "user");
filterChainDefinitionMap.put("/", "user");
filterChainDefinitionMap.put("/login", "kaptchaFilter");
// <!-- 過濾鏈定義,從上向下順序執(zhí)行,一般將 /**放在最為下邊 -->:這是一個(gè)坑呢,一不小心代碼就不好使了;
//這段是配合 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("/static/**", "anon");//配置static文件下資源能被訪問的,這是個(gè)例子
filterChainDefinitionMap.put("/kaptcha.jpg", "anon");//圖片驗(yàn)證碼(kaptcha框架)
filterChainDefinitionMap.put("/api/v1/**", "anon");//API接口
// swagger接口文檔
filterChainDefinitionMap.put("/v2/api-docs", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/doc.html", "anon");
// 其他的
filterChainDefinitionMap.put("/**", "authc");
// 如果不設(shè)置默認(rèn)會(huì)自動(dòng)尋找Web工程根目錄下的"/login.jsp"頁面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登錄成功后要跳轉(zhuǎn)的鏈接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授權(quán)界面,不生效(詳情原因看MyExceptionResolver)
shiroFilterFactoryBean.setUnauthorizedUrl("/errorView/403_error.html");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 設(shè)置realm.
securityManager.setRealm(myShiroRealm());
//注入緩存管理器
//這個(gè)如果執(zhí)行多次,也是同樣的一個(gè)對(duì)象;
securityManager.setCacheManager(ehCacheManager());
//注入記住我管理器;
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* 身份認(rèn)證realm; (這個(gè)需要自己寫,賬號(hào)密碼校驗(yàn);權(quán)限等)
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
/**
* 憑證匹配器 (由于我們的密碼校驗(yàn)交給Shiro的SimpleAuthenticationInfo進(jìn)行處理了
* 所以我們需要修改下doGetAuthenticationInfo中的代碼; @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:這里使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列的次數(shù),比如散列兩次,相當(dāng)于md5(md5(""));
hashedCredentialsMatcher.setHashIterations(2);
//表示是否存儲(chǔ)散列后的密碼為16進(jìn)制,需要和生成密碼時(shí)的一樣,默認(rèn)是base64;
hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
return hashedCredentialsMatcher;
}
/**
* 開啟shiro aop注解支持. 使用代理方式; 所以需要開啟代碼支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* shiro緩存管理器;
* 需要注入對(duì)應(yīng)的其它的實(shí)體類中:
* 安全管理器:securityManager
* 可見securityManager是整個(gè)shiro的核心;
* @return
*/
@Bean
public EhCacheManager ehCacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return cacheManager;
}
/**
* cookie對(duì)象;
* @return
*/
@Bean
public SimpleCookie rememberMeCookie() {
//System.out.println("ShiroConfiguration.rememberMeCookie()");
//這個(gè)參數(shù)是cookie的名稱,對(duì)應(yīng)前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//<!-- 記住我cookie生效時(shí)間30天 ,單位秒;-->
simpleCookie.setMaxAge(259200);
return simpleCookie;
}
/**
* cookie管理對(duì)象;
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager() {
//System.out.println("ShiroConfiguration.rememberMeManager()");
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
return cookieRememberMeManager;
}
@Bean(name = "sessionManager")
public DefaultWebSessionManager defaultWebSessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(18000000);
// url中是否顯示session Id
sessionManager.setSessionIdUrlRewritingEnabled(false);
// 刪除失效的session
sessionManager.setDeleteInvalidSessions(true);
sessionManager.setSessionValidationSchedulerEnabled(true);
sessionManager.setSessionValidationInterval(18000000);
sessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler());
//設(shè)置SessionIdCookie 導(dǎo)致認(rèn)證不成功,不從新設(shè)置新的cookie,從sessionManager獲取sessionIdCookie
//sessionManager.setSessionIdCookie(simpleIdCookie());
sessionManager.getSessionIdCookie().setName("session-z-id");
sessionManager.getSessionIdCookie().setPath("/");
sessionManager.getSessionIdCookie().setMaxAge(60 * 60 * 24 * 7);
return sessionManager;
}
@Bean(name = "sessionValidationScheduler")
public ExecutorServiceSessionValidationScheduler getExecutorServiceSessionValidationScheduler() {
ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler();
scheduler.setInterval(900000);
return scheduler;
}
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}代碼中解釋都非常清楚。
MyShiroRealm.java的內(nèi)容如下:
/**
* Description : 身份校驗(yàn)核心類
*/
public class MyShiroRealm extends AuthorizingRealm {
private static final Logger _logger = LoggerFactory.getLogger(MyShiroRealm.class);
@Autowired
ManagerInfoService managerInfoService;
/**
* 認(rèn)證信息.(身份驗(yàn)證)
* Authentication 是用來驗(yàn)證用戶身份
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
_logger.info("MyShiroRealm.doGetAuthenticationInfo()");
//獲取用戶的輸入的賬號(hào).
String username = (String) token.getPrincipal();
//_logger.info("用戶的賬號(hào):"+username);
//通過username從數(shù)據(jù)庫中查找 ManagerInfo對(duì)象
//實(shí)際項(xiàng)目中,這里可以根據(jù)實(shí)際情況做緩存,如果不做,Shiro自己也是有時(shí)間間隔機(jī)制,2分鐘內(nèi)不會(huì)重復(fù)執(zhí)行該方法
ManagerInfo managerInfo = managerInfoService.findByUsername(username);
if (managerInfo == null) {
return null;
}
//交給AuthenticatingRealm使用CredentialsMatcher進(jìn)行密碼匹配,如果覺得人家的不好可以自定義實(shí)現(xiàn)
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
managerInfo, //用戶
managerInfo.getPassword(), //密碼
ByteSource.Util.bytes(managerInfo.getCredentialsSalt()),//salt=username+salt
getName() //realm name
);
//明文: 若存在,將此用戶存放到登錄認(rèn)證info中,無需自己做密碼對(duì)比,Shiro會(huì)為我們進(jìn)行密碼對(duì)比校驗(yàn)
// SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
// managerInfo, //用戶名
// managerInfo.getPassword(), //密碼
// getName() //realm name
// );
return authenticationInfo;
}
/**
* 此方法調(diào)用hasRole,hasPermission的時(shí)候才會(huì)進(jìn)行回調(diào).
* <p>
* 權(quán)限信息.(授權(quán)):
* 1、如果用戶正常退出,緩存自動(dòng)清空;
* 2、如果用戶非正常退出,緩存自動(dòng)清空;
* 3、如果我們修改了用戶的權(quán)限,而用戶不退出系統(tǒng),修改的權(quán)限無法立即生效。
* (需要手動(dòng)編程進(jìn)行實(shí)現(xiàn);放在service進(jìn)行調(diào)用)
* 在權(quán)限修改后調(diào)用realm中的方法,realm已經(jīng)由spring管理,所以從spring中獲取realm實(shí)例,調(diào)用clearCached方法;
* :Authorization 是授權(quán)訪問控制,用于對(duì)用戶進(jìn)行的操作授權(quán),證明該用戶是否允許進(jìn)行當(dāng)前操作,如訪問某個(gè)鏈接,某個(gè)資源文件等。
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
/*
* 當(dāng)沒有使用緩存的時(shí)候,不斷刷新頁面的話,這個(gè)代碼會(huì)不斷執(zhí)行,
* 當(dāng)其實(shí)沒有必要每次都重新設(shè)置權(quán)限信息,所以我們需要放到緩存中進(jìn)行管理;
* 當(dāng)放到緩存中時(shí),這樣的話,doGetAuthorizationInfo就只會(huì)執(zhí)行一次了,
* 緩存過期之后會(huì)再次執(zhí)行。
*/
_logger.info("權(quán)限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
ManagerInfo managerInfo = (ManagerInfo) principals.getPrimaryPrincipal();
//設(shè)置相應(yīng)角色的權(quán)限信息
for (SysRole role : managerInfo.getRoles()) {
//設(shè)置角色
authorizationInfo.addRole(role.getRole());
for (Permission p : role.getPermissions()) {
//設(shè)置權(quán)限
authorizationInfo.addStringPermission(p.getPermission());
}
}
return authorizationInfo;
}
/**
* 設(shè)置認(rèn)證加密方式
*/
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher md5CredentialsMatcher = new HashedCredentialsMatcher();
md5CredentialsMatcher.setHashAlgorithmName(ShiroKit.HASH_ALGORITHM_NAME);
md5CredentialsMatcher.setHashIterations(ShiroKit.HASH_ITERATIONS);
super.setCredentialsMatcher(md5CredentialsMatcher);
}
}自定義異常處理類MyExceptionResolver.java :
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
//如果是shiro無權(quán)操作,因?yàn)閟hiro 在操作auno等一部分不進(jìn)行轉(zhuǎn)發(fā)至無權(quán)限url
if (ex instanceof UnauthorizedException) {
return new ModelAndView("error/shiro_403");
}
return null;
}
}接口控制
配置好了Shiro后就可以通過注解方式來限制某些接口調(diào)用需要相應(yīng)的角色或權(quán)限了:
@RequestMapping(value = "/index")
@RequiresRoles("admin")
public String index(HttpServletRequest request, Model model) {
_logger.info("進(jìn)入項(xiàng)目管理首頁...");
}其他的注解請(qǐng)參考官網(wǎng)的 Shiro注解
Thymeleaf的shiro標(biāo)簽
可以在Thymeleaf模板中使用shiro的權(quán)限標(biāo)簽來控制某些菜單或按鈕是否顯示。
maven中添加依賴,這個(gè)前面已經(jīng)有了:
<!-- thymeleaf模板中shiro標(biāo)簽-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>在 ShiroConfig 中添加一個(gè)Bean配置:
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}在html頁面添加如下內(nèi)容:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">添加完后在html頁面調(diào)用如下:
<!-- 認(rèn)證通過或已記住的用戶。 -->
<p shiro:user="">
Welcome back John! Not John? Click <a href="login.html">here</a> to login.
</p>
<p shiro:notAuthenticated="">
未身份驗(yàn)證(包括記住我)
</p>
<p shiro:guest=""><span style="white-space:pre;"> </span>Please <a href="login.html">login5555</a> </p>第二種:
<shiro:guest>
<a>登錄</a> <a>注冊(cè)</a>
</shiro:guest>
<shiro:user>
歡迎<shiro:principal property="name"/>
</shiro:user>權(quán)限數(shù)據(jù)庫設(shè)計(jì)
一般來講都會(huì)講用戶的角色和權(quán)限保存到數(shù)據(jù)庫,這里設(shè)計(jì)一種最通用的模型, 使用RBAC(Role-Based Access Control,基于角色的訪問控制)模型設(shè)計(jì)用戶,角色和權(quán)限間的關(guān)系。
簡(jiǎn)單地說, 一個(gè)用戶擁有若干角色,每一個(gè)角色擁有若干權(quán)限。這樣,就構(gòu)造成”用戶-角色-權(quán)限”的授權(quán)模型。
在這種模型中,用戶與角色之間,角色與權(quán)限之間,一般者是多對(duì)多的關(guān)系。如下圖所示:

我們通過MyBatis實(shí)現(xiàn)ManagerInfoService,
/**
* 后臺(tái)用戶管理
*/
@Service
public class ManagerInfoService {
@Resource
private ManagerInfoDao managerInfoDao;
public ManagerInfo findByUsername(String username) {
return managerInfoDao.findByUsername(username);
}
}然后對(duì)應(yīng)的ManagerInfoDao.xml如下:
<resultMap id="ManagerInfoMap" type="managerInfo">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="name" column="name"/>
<result property="password" column="password"/>
<result property="salt" column="salt"/>
<result property="state" column="state"/>
<collection property="roles" ofType="sysRole">
<id property="id" column="role_id"/>
<result property="role" column="role_role"/>
<collection property="permissions" ofType="permission">
<id property="id" column="perm_id"/>
<result property="permission" column="perm_permission"/>
</collection>
</collection>
</resultMap>
<select id="findByUsername" resultMap="ManagerInfoMap">
SELECT DISTINCT
A.id AS id,
A.username AS username,
A.name AS name,
A.password AS password,
A.salt AS salt,
A.state AS state,
C.id AS role_id,
C.role AS role_role,
E.id AS perm_id,
E.permission AS perm_permission
FROM t_manager A
LEFT JOIN t_manager_role B ON A.id=B.manager_id
LEFT JOIN t_role C ON B.role_id=C.id
LEFT JOIN t_role_permission D ON C.id=D.role_id
LEFT JOIN t_permission E ON D.permission_Id=E.id
WHERE username=#{username}
LIMIT 1
</select>ManagerInfo.java如下:
public class ManagerInfo extends Manager implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 一個(gè)管理員具有多個(gè)角色
*/
private List<SysRole> roles;// 一個(gè)用戶具有多個(gè)角色
}到此這篇關(guān)于Java中Shiro安全框架的權(quán)限管理的文章就介紹到這了,更多相關(guān)Shiro框架的權(quán)限管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java設(shè)計(jì)模式詳解之門面模式(外觀模式)
為子系統(tǒng)中的一組接口提供一個(gè)一致的界面, Facade 模式定義了一個(gè)高層接口,這個(gè)接口使得這一子系統(tǒng)更加容易使用。本文給大家介紹Java設(shè)計(jì)模式詳解之門面模式(外觀模式),感興趣的朋友參考下吧2016-04-04
關(guān)于SpringMVC對(duì)Restful風(fēng)格的支持詳解
Restful就是一個(gè)資源定位及資源操作的風(fēng)格,不是標(biāo)準(zhǔn)也不是協(xié)議,只是一種風(fēng)格,是對(duì)http協(xié)議的詮釋,下面這篇文章主要給大家介紹了關(guān)于SpringMVC對(duì)Restful風(fēng)格支持的相關(guān)資料,需要的朋友可以參考下2022-01-01
SpringCloud Feign傳遞HttpServletRequest對(duì)象流程
HttpServletRequest接口的對(duì)象代表客戶端的請(qǐng)求,當(dāng)客戶端通過HTTP協(xié)議訪問Tomcat服務(wù)器時(shí),HTTP請(qǐng)求中的所有信息都封裝在HttpServletRequest接口的對(duì)象中,這篇文章介紹了Feign傳遞HttpServletRequest對(duì)象的流程,感興趣的同學(xué)可以參考下文2023-05-05
Java并發(fā)編程之如何優(yōu)雅關(guān)閉鉤子Shutdown Hook
這篇文章主要為大家詳細(xì)介紹了Java如何實(shí)現(xiàn)優(yōu)雅關(guān)閉鉤子Shutdown Hook,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-04-04
java讀取枚舉類的值轉(zhuǎn)成list和map方式
這篇文章主要介紹了java讀取枚舉類的值轉(zhuǎn)成list和map方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
簡(jiǎn)單了解JAVA內(nèi)存區(qū)域效果知識(shí)
這篇文章主要介紹了簡(jiǎn)單了解JAVA內(nèi)存區(qū)域效果知識(shí),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10

