ehcache使用_Java 程序员如何使用 Shiro 框架
Java 程序員如何使用 Shiro 框架
一、架構
要學習如何使用Shiro必須先從它的架構談起,作為一款安全框架Shiro的設計相當精妙。Shiro的應用不依賴任何容器,它也可以在JavaSE下使用。但是最常用的環境還是JavaEE。下面以用戶登錄為例:
1、使用用戶的登錄信息創建令牌UsernamePasswordToken?token?=?new?UsernamePasswordToken(username,?password);token可以理解為用戶令牌,登錄的過程被抽象為Shiro驗證令牌是否具有合法身份以及相關權限。
2、執行登陸動作SecurityUtils.setSecurityManager(securityManager);?// 注入SecurityManagerSubject subject = SecurityUtils.getSubject();?// 獲取Subject單例對象
subject.login(token);?// 登陸
Shiro的核心部分是SecurityManager,它負責安全認證與授權。Shiro本身已經實現了所有的細節,用戶可以完全把它當做一個黑盒來使用。SecurityUtils對象,本質上就是一個工廠類似Spring中的ApplicationContext。
Subject是初學者比較難于理解的對象,很多人以為它可以等同于User,其實不然。Subject中文翻譯:項目,而正確的理解也恰恰如此。它是你目前所設計的需要通過Shiro保護的項目的一個抽象概念。通過令牌(token)與項目(subject)的登陸(login)關系,Shiro保證了項目整體的安全。
3、判斷用戶
Shiro本身無法知道所持有令牌的用戶是否合法,因為除了項目的設計人員恐怕誰都無法得知。因此Realm是整個框架中為數不多的必須由設計者自行實現的模塊,當然Shiro提供了多種實現的途徑,本文只介紹最常見也最重要的一種實現方式——數據庫查詢。
4、兩條重要的英文
我在學習Shiro的過程中遇到的第一個障礙就是這兩個對象的英文名稱:AuthorizationInfo,AuthenticationInfo。不用懷疑自己的眼睛,它們確實長的很像,不但長的像,就連意思都十分近似。
在解釋它們前首先必須要描述一下Shiro對于安全用戶的界定:和大多數操作系統一樣。用戶具有角色和權限兩種最基本的屬性。例如,我的Windows登陸名稱是learnhow,它的角色是administrator,而administrator具有所有系統權限。這樣learnhow自然就擁有了所有系統權限。那么其他人需要登錄我的電腦怎么辦,我可以開放一個guest角色,任何無法提供正確用戶名與密碼的未知用戶都可以通過guest來登錄,而系統對于guest角色開放的權限極其有限。
同理,Shiro對用戶的約束也采用了這樣的方式。AuthenticationInfo代表了用戶的角色信息集合,AuthorizationInfo代表了角色的權限信息集合。如此一來,當設計人員對項目中的某一個url路徑設置了只允許某個角色或具有某種權限才可以訪問的控制約束的時候,Shiro就可以通過以上兩個對象來判斷。說到這里,大家可能還比較困惑。先不要著急,繼續往后看就自然會明白了。
二、實現Realm
如何實現Realm是本文的重頭戲,也是比較費事的部分。這里大家會接觸到幾個新鮮的概念:緩存機制、散列算法、加密算法。由于本文不會專門介紹這些概念,所以這里僅僅拋磚引玉的談幾點,能幫助大家更好的理解Shiro即可。
1、緩存機制
Ehcache是很多Java項目中使用的緩存框架,Hibernate就是其中之一。它的本質就是將原本只能存儲在內存中的數據通過算法保存到硬盤上,再根據需求依次取出。你可以把Ehcache理解為一個Map對象,通過put保存對象,再通過get取回對象。xml version="1.0"?encoding="UTF-8"?><ehcache?name="shirocache"><diskStore?path="java.io.tmpdir"?/><cache?name="passwordRetryCache"maxEntriesLocalHeap="2000"eternal="false"timeToIdleSeconds="1800"timeToLiveSeconds="0"overflowToDisk="false"statistics="true">cache>ehcache>以上是ehcache.xml文件的基礎配置,timeToLiveSeconds為緩存的最大生存時間,timeToIdleSeconds為緩存的最大空閑時間,當eternal為false時ttl和tti才可以生效。更多配置的含義大家可以去網上查詢。2、散列算法與加密算法
md5是本文會使用的散列算法,加密算法本文不會涉及。散列和加密本質上都是將一個Object變成一串無意義的字符串,不同點是經過散列的對象無法復原,是一個單向的過程。例如,對密碼的加密通常就是使用散列算法,因此用戶如果忘記密碼只能通過修改而無法獲取原始密碼。但是對于信息的加密則是正規的加密算法,經過加密的信息是可以通過秘鑰解密和還原。
3、用戶注冊
請注意,雖然我們一直在談論用戶登錄的安全性問題,但是說到用戶登錄首先就是用戶注冊。如何保證用戶注冊的信息不丟失,不泄密也是項目設計的重點。
public?class?PasswordHelper?{private?RandomNumberGenerator randomNumberGenerator =?new?SecureRandomNumberGenerator();private?String algorithmName =?"md5";private?final?int?hashIterations =?2;public?void?encryptPassword(User user)?{// User對象包含最基本的字段Username和Password???????user.setSalt(randomNumberGenerator.nextBytes().toHex());// 將用戶的注冊密碼經過散列算法替換成一個不可逆的新密碼保存進數據,散列過程使用了鹽
???????String newPassword =?new?SimpleHash(algorithmName, user.getPassword(),
???????????????ByteSource.Util.bytes(user.getCredentialsSalt()), hashIterations).toHex();
???????user.setPassword(newPassword);
???}
}
如果你不清楚什么叫加鹽可以忽略散列的過程,只要明白存儲在數據庫中的密碼是根據戶注冊時填寫的密碼所產生的一個新字符串就可以了。經過散列后的密碼替換用戶注冊時的密碼,然后將User保存進數據庫。剩下的工作就丟給UserService來處理。
那么這樣就帶來了一個新問題,既然散列算法是無法復原的,當用戶登錄的時候使用當初注冊時的密碼,我們又應該如何判斷?答案就是需要對用戶密碼再次以相同的算法散列運算一次,再同數據庫中保存的字符串比較。
4、匹配
CredentialsMatcher是一個接口,功能就是用來匹配用戶登錄使用的令牌和數據庫中保存的用戶信息是否匹配。當然它的功能不僅如此。本文要介紹的是這個接口的一個實現類:HashedCredentialsMatcher
public?class?RetryLimitHashedCredentialsMatcher?extends?HashedCredentialsMatcher?{// 聲明一個緩存接口,這個接口是Shiro緩存管理的一部分,它的具體實現可以通過外部容器注入private?Cache passwordRetryCache;public?RetryLimitHashedCredentialsMatcher(CacheManager cacheManager)?{???????passwordRetryCache = cacheManager.getCache("passwordRetryCache");
???}@Overridepublic?boolean?doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)?{
???????String username = (String) token.getPrincipal();
???????AtomicInteger retryCount = passwordRetryCache.get(username);if?(retryCount ==?null) {
???????????retryCount =?new?AtomicInteger(0);
???????????passwordRetryCache.put(username, retryCount);
???????}// 自定義一個驗證過程:當用戶連續輸入密碼錯誤5次以上禁止用戶登錄一段時間if?(retryCount.incrementAndGet() >?5) {throw?new?ExcessiveAttemptsException();
???????}boolean?match =?super.doCredentialsMatch(token, info);if?(match) {
???????????passwordRetryCache.remove(username);
???????}return?match;
???}
}
可以看到,這個實現里設計人員僅僅是增加了一個不允許連續錯誤登錄的判斷。真正匹配的過程還是交給它的直接父類去完成。連續登錄錯誤的判斷依靠Ehcache緩存來實現。顯然match返回true為匹配成功。
5、獲取用戶的角色和權限信息
說了這么多才到我們的重點Realm,如果你已經理解了Shiro對于用戶匹配和注冊加密的全過程,真正理解Realm的實現反而比較簡單。我們還得回到上文提及的兩個非常類似的對象AuthorizationInfo和AuthenticationInfo。因為Realm就是提供這兩個對象的地方。
public?class?UserRealm?extends?AuthorizingRealm {// 用戶對應的角色信息與權限信息都保存在數據庫中,通過UserService獲取數據private?UserService userService =?new?UserServiceImpl();/**????* 提供用戶信息返回權限信息
????*/@Overrideprotected?AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String?username = (String) principals.getPrimaryPrincipal();
???????SimpleAuthorizationInfo authorizationInfo =?new?SimpleAuthorizationInfo();// 根據用戶名查詢當前用戶擁有的角色
???????Set roles = userService.findRoles(username);
???????Set<String> roleNames =?new?HashSet<String>();for?(Role role : roles) {
???????????roleNames.add(role.getRole());
???????}// 將角色名稱提供給info
???????authorizationInfo.setRoles(roleNames);// 根據用戶名查詢當前用戶權限
???????Set permissions = userService.findPermissions(username);
???????Set<String> permissionNames =?new?HashSet<String>();for?(Permission permission : permissions) {
???????????permissionNames.add(permission.getPermission());
???????}// 將權限名稱提供給info
???????authorizationInfo.setStringPermissions(permissionNames);return?authorizationInfo;
???}/**
????* 提供賬戶信息返回認證信息
????*/@Overrideprotected?AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String?username = (String) token.getPrincipal();
???????User user = userService.findByUsername(username);if?(user ==?null) {// 用戶名不存在拋出異常throw?new?UnknownAccountException();
???????}if?(user.getLocked() ==?0) {// 用戶被管理員鎖定拋出異常throw?new?LockedAccountException();
???????}
???????SimpleAuthenticationInfo authenticationInfo =?new?SimpleAuthenticationInfo(user.getUsername(),
???????????????user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName());return?authenticationInfo;
???}
}
根據Shiro的設計思路,用戶與角色之前的關系為多對多,角色與權限之間的關系也是多對多。在數據庫中需要因此建立5張表,分別是:
- 用戶表(存儲用戶名,密碼,鹽等)
- 角色表(角色名稱,相關描述等)
- 權限表(權限名稱,相關描述等)
- 用戶-角色對應中間表(以用戶ID和角色ID作為聯合主鍵)
- 角色-權限對應中間表(以角色ID和權限ID作為聯合主鍵)
???????????????????????http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
???????????????????????http://www.springframework.org/schema/context
???????????????????????http://www.springframework.org/schema/context/spring-context-3.1.xsd
???????????????????????http://www.springframework.org/schema/mvc
???????????????????????http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd"><bean?id="cacheManager"?class="org.apache.shiro.cache.ehcache.EhCacheManager"><property?name="cacheManagerConfigFile"?value="classpath:ehcache.xml"?/>bean><bean?id="credentialsMatcher"?class="utils.RetryLimitHashedCredentialsMatcher"><constructor-arg?ref="cacheManager"?/><property?name="hashAlgorithmName"?value="md5"?/><property?name="hashIterations"?value="2"?/><property?name="storedCredentialsHexEncoded"?value="true"?/>bean><bean?id="userRealm"?class="utils.UserRealm"><property?name="credentialsMatcher"?ref="credentialsMatcher"?/>bean><bean?id="securityManager"?class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property?name="realm"?ref="userRealm"?/>bean><bean?id="shiroFilter"?class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property?name="securityManager"?ref="securityManager"?/><property?name="loginUrl"?value="/"?/><property?name="unauthorizedUrl"?value="/"?/><property?name="filterChainDefinitions"><value>
???????????????/authc/admin = roles[admin]
???????????????/authc/** = authc
???????????????/** = anonvalue>property>bean><bean?id="lifecycleBeanPostProcessor"?class="org.apache.shiro.spring.LifecycleBeanPostProcessor"?/>beans>需要注意filterChainDefinitions過濾器中對于路徑的配置是有順序的,當找到匹配的條目之后容器不會再繼續尋找。因此帶有通配符的路徑要放在后面。三條配置的含義是:
- ?/authc/admin需要用戶有用admin權限
- /authc/**用戶必須登錄才能訪問
- /**其他所有路徑任何人都可以訪問
???????UsernamePasswordToken token =?new?UsernamePasswordToken(username, password);
???????Subject subject = SecurityUtils.getSubject();try?{
???????????subject.login(token);
???????}?catch?(IncorrectCredentialsException ice) {// 捕獲密碼錯誤異常
???????????ModelAndView mv =?new?ModelAndView("error");
???????????mv.addObject("message",?"password error!");return?mv;
???????}?catch?(UnknownAccountException uae) {// 捕獲未知用戶名異常
???????????ModelAndView mv =?new?ModelAndView("error");
???????????mv.addObject("message",?"username error!");return?mv;
???????}?catch?(ExcessiveAttemptsException eae) {// 捕獲錯誤登錄過多的異常
???????????ModelAndView mv =?new?ModelAndView("error");
???????????mv.addObject("message",?"times error");return?mv;
???????}
???????User user = userService.findByUsername(username);
???????subject.getSession().setAttribute("user", user);return?new?ModelAndView("success");
???}
}登錄完成以后,當前用戶信息被保存進Session。這個Session是通過Shiro管理的會話對象,要獲取依然必須通過Shiro。傳統的Session中不存在User對象。@Controller@RequestMapping("authc")public?class?AuthcController?{// /authc/** = authc 任何通過表單登錄的用戶都可以訪問@RequestMapping("anyuser")public?ModelAndView?anyuser()?{
???????Subject subject = SecurityUtils.getSubject();
???????User user = (User) subject.getSession().getAttribute("user");
???????System.out.println(user);return?new?ModelAndView("inner");
???}// /authc/admin = user[admin] 只有具備admin角色的用戶才可以訪問,否則請求將被重定向至登錄界面@RequestMapping("admin")public?ModelAndView?admin()?{
???????Subject subject = SecurityUtils.getSubject();
???????User user = (User) subject.getSession().getAttribute("user");
???????System.out.println(user);return?new?ModelAndView("inner");
???}
}
總結
以上是生活随笔為你收集整理的ehcache使用_Java 程序员如何使用 Shiro 框架的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 触发器及其应用实验报告总结_双面喷绘材料
- 下一篇: bootstarp js设置列隐藏_Bo