Java中Shiro安全框架的權(quán)限管理
前言
Apache Shiro是Java的一個(gè)安全框架。
目前,使用Apache Shiro的人越來(lái)越多,相比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ù)庫(kù),而不是明文存儲(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è)是非常常見的功能,即一次登錄后,下次再來(lái)的話不用登錄了。
記住一點(diǎn),Shiro不會(huì)去維護(hù)用戶、維護(hù)權(quán)限,這些需要我們自己去設(shè)計(jì)/提供,然后通過相應(yīng)的接口注入給Shiro即可。
接下來(lái)我們分別從外部和內(nèi)部來(lái)看看Shiro的架構(gòu),對(duì)于一個(gè)好的框架,從外部來(lái)看應(yīng)該具有非常簡(jiǎn)單易于使用的API, 且API契約明確;從內(nèi)部來(lái)看的話,其應(yīng)該有一個(gè)可擴(kuò)展的架構(gòu),即非常容易插入用戶自定義實(shí)現(xiàn),因?yàn)槿魏慰蚣芏疾荒軡M足所有需求。
可以看到:應(yīng)用代碼直接交互的對(duì)象是Subject,也就是說(shuō)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)限),就是說(shuō)SecurityManager要驗(yàn)證用戶身份,那么它需要從Realm獲取相應(yīng)的用戶進(jìn)行比較以確定用戶身份是否合法;也需要從Realm得到用戶相應(yīng)的角色/權(quán)限進(jìn)行驗(yàn)證用戶是否能進(jìn)行操作;可以把Realm看成DataSource,即安全數(shù)據(jù)源。
也就是說(shuō)對(duì)于我們而言,最簡(jiǎn)單的一個(gè)Shiro應(yīng)用:
1、應(yīng)用代碼通過Subject 來(lái)進(jìn)行認(rèn)證和授權(quán),而 Subject 又委托給 SecurityManager;
2、我們需要給Shiro的 SecurityManager 注入 Realm,從而讓 SecurityManager 能得到合法的用戶及其權(quán)限進(jìn)行判斷。
從以上也可以看出,Shiro不提供維護(hù)用戶/權(quán)限,而是通過Realm讓開發(fā)人員自己注入。
接下來(lái)我們來(lái)從Shiro內(nèi)部來(lá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)器,或者訪問控制器,用來(lái)決定主體是否有權(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來(lái)管理主體與應(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ù)庫(kù),那么可以實(shí)現(xiàn)自己的SessionDAO,通過如JDBC寫到數(shù)據(jù)庫(kù);比如想把Session放到Memcached中,可以實(shí)現(xiàn)自己的Memcached SessionDAO,另外SessionDAO中可以使用Cache進(jìn)行緩存,以提高性能;
- CacheManager:緩存控制器,來(lái)管理如用戶、角色、權(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 來(lái)實(shí)現(xiàn),就好像SpringMvc 通過DispachServlet 來(lái)主控制一樣。 * 既然是使用 Filter 一般也就能猜到,是通過URL規(guī)則來(lái)進(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定義說(shuō)明 * 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ù)器健康狀況頁(yè)面 // filterChainDefinitionMap.put("/info", "roles[aix]");//服務(wù)器信息頁(yè)面 // 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"頁(yè)面 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 是用來(lái)驗(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ù)庫(kù)中查找 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中,無(wú)需自己做密碼對(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)限無(wú)法立即生效。 * (需要手動(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í)候,不斷刷新頁(yè)面的話,這個(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無(wú)權(quán)操作,因?yàn)閟hiro 在操作auno等一部分不進(jìn)行轉(zhuǎn)發(fā)至無(wú)權(quán)限url if (ex instanceof UnauthorizedException) { return new ModelAndView("error/shiro_403"); } return null; } }
接口控制
配置好了Shiro后就可以通過注解方式來(lái)限制某些接口調(diào)用需要相應(yīng)的角色或權(quán)限了:
@RequestMapping(value = "/index") @RequiresRoles("admin") public String index(HttpServletRequest request, Model model) { _logger.info("進(jìn)入項(xiàng)目管理首頁(yè)..."); }
其他的注解請(qǐng)參考官網(wǎng)的 Shiro注解
Thymeleaf的shiro標(biāo)簽
可以在Thymeleaf模板中使用shiro的權(quán)限標(biāo)簽來(lái)控制某些菜單或按鈕是否顯示。
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頁(yè)面添加如下內(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頁(yè)面調(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ù)庫(kù)設(shè)計(jì)
一般來(lái)講都會(huì)講用戶的角色和權(quán)限保存到數(shù)據(jù)庫(kù),這里設(shè)計(jì)一種最通用的模型, 使用RBAC(Role-Based Access Control,基于角色的訪問控制)模型設(shè)計(jì)用戶,角色和權(quán)限間的關(guān)系。
簡(jiǎn)單地說(shuō), 一個(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 中的FileReader和FileWriter源碼分析_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
本文給大家分享一段示例程序,通過示例代碼可以看出FileReader是基于InputStreamReader實(shí)現(xiàn)的,FileWriter是基于OutputStreamWriter實(shí)現(xiàn)的,具體程序代碼大家通過本文了解下吧2017-05-05SpringBoot3實(shí)戰(zhàn)教程之實(shí)現(xiàn)接口簽名驗(yàn)證功能
接口簽名是一種重要的安全機(jī)制,用于確保 API 請(qǐng)求的真實(shí)性、數(shù)據(jù)的完整性以及防止重放攻擊,這篇文章主要介紹了SpringBoot3實(shí)戰(zhàn)教程之實(shí)現(xiàn)接口簽名驗(yàn)證功能,需要的朋友可以參考下2025-04-043分鐘純 Java 注解搭個(gè)管理系統(tǒng)的示例代碼
這篇文章主要介紹了3分鐘純 Java 注解搭個(gè)管理系統(tǒng)的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03深入理解Mybatis中的resultType和resultMap
這篇文章給大家介紹了mybatis中的resultType和resultMap的用法實(shí)例講解,MyBatis中在查詢進(jìn)行select映射的時(shí)候,返回類型可以用resultType,也可以用resultMap,至于兩種用法區(qū)別,通過本文一起學(xué)習(xí)吧2016-09-09淺談線性表的原理及簡(jiǎn)單實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇淺談線性表的原理及簡(jiǎn)單實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2017-06-06Java pom.xml parent引用報(bào)錯(cuò)問題解決方案
這篇文章主要介紹了Java pom.xml parent引用報(bào)錯(cuò)問題解決方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08淺析Java設(shè)計(jì)模式編程中的單例模式和簡(jiǎn)單工廠模式
這篇文章主要介紹了淺析Java設(shè)計(jì)模式編程中的單例模式和簡(jiǎn)單工廠模式,使用設(shè)計(jì)模式編寫代碼有利于團(tuán)隊(duì)協(xié)作時(shí)程序的維護(hù),需要的朋友可以參考下2016-01-01