Java中Shiro安全框架的權(quán)限管理
前言
Apache Shiro是Java的一個安全框架。
目前,使用Apache Shiro的人越來越多,相比Spring Security而言相當簡單, 可能沒有Spring Security做的功能強大,但是在實際工作時可能并不需要那么復(fù)雜的東西, 所以使用小而簡單的Shiro就足夠了。
對于它倆到底哪個好,這個不必糾結(jié),能更簡單的解決項目問題就好了。
本文只介紹基本的Shiro使用,不會過多分析源碼等,重在使用。
Shiro架構(gòu)
Shiro可以非常容易的開發(fā)出足夠好的應(yīng)用,其不僅可以用在JavaSE環(huán)境,也可以用在JavaEE環(huán)境。
Shiro可以幫助我們完成:認證、授權(quán)、加密、會話管理、與Web集成、緩存等。
Shiro的API非常簡單,其基本功能點如下圖所示:
- Authentication:身份認證/登錄,驗證用戶是不是擁有相應(yīng)的身份;
- Authorization:授權(quán),即權(quán)限驗證,驗證某個已認證的用戶是否擁有某個權(quán)限;即判斷用戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色?;蛘呒毩6鹊尿炞C某個用戶對某個資源是否具有某個權(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),因為任何框架都不能滿足所有需求。
可以看到:應(yīng)用代碼直接交互的對象是Subject,也就是說Shiro的對外API核心就是 Subject。
- Subject:主體,代表了當前”用戶”,這個用戶不一定是一個具體的人,與當前應(yīng)用交互的任何東西都是Subject,如網(wǎng)絡(luò)爬蟲,機器人等;即一個抽象概念;所有Subject都綁定到SecurityManager,與Subject的所有交互都會委托給SecurityManager;可以把Subject認為是一個門面;SecurityManager才是實際的執(zhí)行者;
- SecurityManager:安全管理器;即所有與安全有關(guān)的操作都會與SecurityManager交互;且它管理著所有Subject;可以看出它是Shiro的核心,它負責(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 來進行認證和授權(quán),而 Subject 又委托給 SecurityManager;
2、我們需要給Shiro的 SecurityManager 注入 Realm,從而讓 SecurityManager 能得到合法的用戶及其權(quán)限進行判斷。
從以上也可以看出,Shiro不提供維護用戶/權(quán)限,而是通過Realm讓開發(fā)人員自己注入。
接下來我們來從Shiro內(nèi)部來看下Shiro的架構(gòu),如下圖所示:
- Subject:主體,可以看到主體可以是任何可以與應(yīng)用交互的”用戶”;
- SecurityManager:相當于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心臟;所有具體的交互都通過SecurityManager進行控制;它管理著所有Subject、且負責(zé)進行認證和授權(quán)、及會話、緩存的管理。
- Authenticator:認證器,負責(zé)主體認證的,這是一個擴展點,如果用戶覺得Shiro默認的不好,可以自定義實現(xiàn);其需要認證策略(Authentication Strategy),即什么情況下算用戶認證通過了;
- Authorizer:授權(quán)器,或者訪問控制器,用來決定主體是否有權(quán)限進行相應(yīng)的操作;即控制著用戶能訪問應(yīng)用中的哪些功能;
- Realm:可以有1個或多個Realm,可以認為是安全實體數(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提高了一些常見的加密組件用于如密碼加密/解密的。
SpringBoot 集成
實現(xiàn)一個最常見的驗證碼登錄、記住我、權(quán)限自定義(or),緩存功能的SpringBoot應(yīng)用,模板使用Thymeleaf3
Maven依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- thymeleaf模板中shiro標簽--> <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> <!--驗證碼框架--> <dependency> <groupId>com.github.axet</groupId> <artifactId>kaptcha</artifactId> <version>0.0.9</version> </dependency>
新建一個配置類ShiroConfig.java
,內(nèi)容如下:
/** * Description : Apache Shiro 核心通過 Filter 來實現(xiàn),就好像SpringMvc 通過DispachServlet 來主控制一樣。 * 既然是使用 Filter 一般也就能猜到,是通過URL規(guī)則來進行過濾和權(quán)限校驗,所以我們需要定義一系列關(guān)于URL的規(guī)則和訪問權(quán)限。 */ @Configuration @Order(1) public class ShiroConfig { //配置kaptcha圖片驗證碼框架提供的Servlet,,這是個坑,很多人忘記注冊(注意) @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 是否有邊框 默認為true 我們可以自己設(shè)置yes,no // kaptcha.border.color 邊框顏色 默認為Color.BLACK // kaptcha.border.thickness 邊框粗細度 默認為1 // kaptcha.producer.impl 驗證碼生成器 默認為DefaultKaptcha // kaptcha.textproducer.impl 驗證碼文本生成器 默認為DefaultTextCreator // kaptcha.textproducer.char.string 驗證碼文本字符內(nèi)容范圍 默認為abcde2345678gfynmnpwx // kaptcha.textproducer.char.length 驗證碼文本字符長度 默認為5 // kaptcha.textproducer.font.names 驗證碼文本字體樣式 默認為new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) // kaptcha.textproducer.font.size 驗證碼文本字符大小 默認為40 // kaptcha.textproducer.font.color 驗證碼文本字符顏色 默認為Color.BLACK // kaptcha.textproducer.char.space 驗證碼文本字符間距 默認為2 // kaptcha.noise.impl 驗證碼噪點生成對象 默認為DefaultNoise // kaptcha.noise.color 驗證碼噪點顏色 默認為Color.BLACK // kaptcha.obscurificator.impl 驗證碼樣式引擎 默認為WaterRipple // kaptcha.word.impl 驗證碼文本字符渲染 默認為DefaultWordRenderer // kaptcha.background.impl 驗證碼背景生成器 默認為DefaultBackground // kaptcha.background.clear.from 驗證碼背景顏色漸進 默認為Color.LIGHT_GRAY // kaptcha.background.clear.to 驗證碼背景顏色漸進 默認為Color.WHITE // kaptcha.image.width 驗證碼圖片寬度 默認為200 // kaptcha.image.height 驗證碼圖片高度 默認為50 return servlet; } //注入異常處理類 @Bean public MyExceptionResolver myExceptionResolver() { return new MyExceptionResolver(); } /** * ShiroFilterFactoryBean 處理攔截資源文件問題。 * 注意:單獨一個ShiroFilterFactoryBean配置是或報錯的,以為在 * 初始化ShiroFilterFactoryBean的時候需要注入:SecurityManager Filter Chain定義說明 * 1、一個URL可以配置多個Filter,使用逗號分隔 * 2、當設(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ù)請求的方法,相當于/admins/user/**=perms[user:method] ,其中method為post,get,delete等。 //port:比如/admins/user/**=port[8081],當請求的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:*"],當有多個參數(shù)時必須每個參數(shù)都通過才通過,想當于isPermitedAll()方法。 //roles:比如/admins/user/**=roles[admin],參數(shù)可以寫多個,多個時必須加上引號,并且參數(shù)之間用逗號分割,當有多個參數(shù)時,比如/admins/user/**=roles["admin,guest"],每個參數(shù)通過才算通過,相當于hasAllRoles()方法。//要實現(xiàn)or的效果看http://zgzty.blog.163.com/blog/static/83831226201302983358670/ //anon:比如/admins/**=anon 沒有參數(shù),表示可以匿名使用。 //authc:比如/admins/user/**=authc表示需要認證才能使用,沒有參數(shù) //authcBasic:比如/admins/user/**=authcBasic沒有參數(shù)表示httpBasic認證 //ssl:比如/admins/user/**=ssl沒有參數(shù),表示安全的url請求,協(xié)議為https //user:比如/admins/user/**=user沒有參數(shù)表示必須存在用戶,當?shù)侨氩僮鲿r不做檢查 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置退出過濾器,其中的具體的退出代碼Shiro已經(jīng)替我們實現(xiàn)了 filterChainDefinitionMap.put("/logout", "logout"); //配置記住我或認證通過可以訪問的地址 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)站圖標 filterChainDefinitionMap.put("/static/**", "anon");//配置static文件下資源能被訪問的,這是個例子 filterChainDefinitionMap.put("/kaptcha.jpg", "anon");//圖片驗證碼(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è)置默認會自動尋找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()); //注入緩存管理器 //這個如果執(zhí)行多次,也是同樣的一個對象; securityManager.setCacheManager(ehCacheManager()); //注入記住我管理器; securityManager.setRememberMeManager(rememberMeManager()); return securityManager; } /** * 身份認證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(); // 散列算法:這里使用MD5算法; hashedCredentialsMatcher.setHashAlgorithmName("md5"); // 散列的次數(shù),比如散列兩次,相當于md5(md5("")); hashedCredentialsMatcher.setHashIterations(2); //表示是否存儲散列后的密碼為16進制,需要和生成密碼時的一樣,默認是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緩存管理器; * 需要注入對應(yīng)的其它的實體類中: * 安全管理器:securityManager * 可見securityManager是整個shiro的核心; * @return */ @Bean public EhCacheManager ehCacheManager() { EhCacheManager cacheManager = new EhCacheManager(); cacheManager.setCacheManagerConfigFile("classpath:ehcache.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; } @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)致認證不成功,不從新設(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 : 身份校驗核心類 */ public class MyShiroRealm extends AuthorizingRealm { private static final Logger _logger = LoggerFactory.getLogger(MyShiroRealm.class); @Autowired ManagerInfoService managerInfoService; /** * 認證信息.(身份驗證) * Authentication 是用來驗證用戶身份 * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { _logger.info("MyShiroRealm.doGetAuthenticationInfo()"); //獲取用戶的輸入的賬號. String username = (String) token.getPrincipal(); //_logger.info("用戶的賬號:"+username); //通過username從數(shù)據(jù)庫中查找 ManagerInfo對象 //實際項目中,這里可以根據(jù)實際情況做緩存,如果不做,Shiro自己也是有時間間隔機制,2分鐘內(nèi)不會重復(fù)執(zhí)行該方法 ManagerInfo managerInfo = managerInfoService.findByUsername(username); 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 ); //明文: 若存在,將此用戶存放到登錄認證info中,無需自己做密碼對比,Shiro會為我們進行密碼對比校驗 // SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( // managerInfo, //用戶名 // managerInfo.getPassword(), //密碼 // getName() //realm name // ); return authenticationInfo; } /** * 此方法調(diào)用hasRole,hasPermission的時候才會進行回調(diào). * <p> * 權(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),證明該用戶是否允許進行當前操作,如訪問某個鏈接,某個資源文件等。 * * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { /* * 當沒有使用緩存的時候,不斷刷新頁面的話,這個代碼會不斷執(zhí)行, * 當其實沒有必要每次都重新設(shè)置權(quán)限信息,所以我們需要放到緩存中進行管理; * 當放到緩存中時,這樣的話,doGetAuthorizationInfo就只會執(zhí)行一次了, * 緩存過期之后會再次執(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è)置認證加密方式 */ @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)操作,因為shiro 在操作auno等一部分不進行轉(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("進入項目管理首頁..."); }
其他的注解請參考官網(wǎng)的 Shiro注解
Thymeleaf的shiro標簽
可以在Thymeleaf模板中使用shiro的權(quán)限標簽來控制某些菜單或按鈕是否顯示。
maven中添加依賴,這個前面已經(jīng)有了:
<!-- thymeleaf模板中shiro標簽--> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
在 ShiroConfig 中添加一個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)用如下:
<!-- 認證通過或已記住的用戶。 --> <p shiro:user=""> Welcome back John! Not John? Click <a href="login.html">here</a> to login. </p> <p shiro:notAuthenticated=""> 未身份驗證(包括記住我) </p> <p shiro:guest=""><span style="white-space:pre;"> </span>Please <a href="login.html">login5555</a> </p>
第二種:
<shiro:guest> <a>登錄</a> <a>注冊</a> </shiro:guest> <shiro:user> 歡迎<shiro:principal property="name"/> </shiro:user>
權(quán)限數(shù)據(jù)庫設(shè)計
一般來講都會講用戶的角色和權(quán)限保存到數(shù)據(jù)庫,這里設(shè)計一種最通用的模型, 使用RBAC(Role-Based Access Control,基于角色的訪問控制)模型設(shè)計用戶,角色和權(quán)限間的關(guān)系。
簡單地說, 一個用戶擁有若干角色,每一個角色擁有若干權(quán)限。這樣,就構(gòu)造成”用戶-角色-權(quán)限”的授權(quán)模型。
在這種模型中,用戶與角色之間,角色與權(quán)限之間,一般者是多對多的關(guān)系。如下圖所示:
我們通過MyBatis實現(xiàn)ManagerInfoService
,
/** * 后臺用戶管理 */ @Service public class ManagerInfoService { @Resource private ManagerInfoDao managerInfoDao; public ManagerInfo findByUsername(String username) { return managerInfoDao.findByUsername(username); } }
然后對應(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; /** * 一個管理員具有多個角色 */ private List<SysRole> roles;// 一個用戶具有多個角色 }
到此這篇關(guān)于Java中Shiro安全框架的權(quán)限管理的文章就介紹到這了,更多相關(guān)Shiro框架的權(quán)限管理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 中的FileReader和FileWriter源碼分析_動力節(jié)點Java學(xué)院整理
本文給大家分享一段示例程序,通過示例代碼可以看出FileReader是基于InputStreamReader實現(xiàn)的,FileWriter是基于OutputStreamWriter實現(xiàn)的,具體程序代碼大家通過本文了解下吧2017-05-05SpringBoot3實戰(zhàn)教程之實現(xiàn)接口簽名驗證功能
接口簽名是一種重要的安全機制,用于確保 API 請求的真實性、數(shù)據(jù)的完整性以及防止重放攻擊,這篇文章主要介紹了SpringBoot3實戰(zhàn)教程之實現(xiàn)接口簽名驗證功能,需要的朋友可以參考下2025-04-04深入理解Mybatis中的resultType和resultMap
這篇文章給大家介紹了mybatis中的resultType和resultMap的用法實例講解,MyBatis中在查詢進行select映射的時候,返回類型可以用resultType,也可以用resultMap,至于兩種用法區(qū)別,通過本文一起學(xué)習(xí)吧2016-09-09淺析Java設(shè)計模式編程中的單例模式和簡單工廠模式
這篇文章主要介紹了淺析Java設(shè)計模式編程中的單例模式和簡單工廠模式,使用設(shè)計模式編寫代碼有利于團隊協(xié)作時程序的維護,需要的朋友可以參考下2016-01-01