用Shiro作登陸權限控制時,密碼加密是自定義的。java
數據庫的密碼經過散列獲取,以下,算法爲:md5,鹽爲一個隨機數字,散列迭代次數爲3次,最終將salt與散列後的密碼保存到數據庫內,第二次登陸時將登陸的令牌再進行一樣的運算後再與數據庫的作對比。web
String algorithmName = "md5";
String userName = "rose";
String password = "rose123";
int hashIterations = 3; //散列迭代次數
String salt = new SecureRandomNumberGenerator().nextBytes().toHex();
// 將用戶的密碼通過散列算法替換成一個不可逆的新密碼保存進數據,散列過程使用了鹽
SimpleHash simpleHash = new SimpleHash(algorithmName,password,userName+salt,hashIterations);
String encodedPassword = simpleHash.toHex();
System.out.println("salt is "+salt);
System.out.println("encodedPassword is "+encodedPassword);
建立RetryLimitHashedCredentialsMatcher類,此類有登陸失敗次數的判斷,多於5次後再等待10分鐘後才能重試。算法
緩存機制用到了Ehcache,Ehcache是不少Java項目中使用的緩存框架,Hibernate就是其中之一。它的本質就是將本來只能存儲在內存中的數據經過算法保存到硬盤上,再根據需求依次取出。你能夠把Ehcache理解爲一個Map<String,Object>對象,經過put保存對象,再經過get取回對象。spring
package com.ken.shiro; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.ExcessiveAttemptsException; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheManager; import java.util.concurrent.atomic.AtomicInteger; public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher { private Cache<String,AtomicInteger> passwordRetryCache; public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager){ passwordRetryCache = cacheManager.getCache("passwordRetryCache"); } @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { String userName = token.getPrincipal().toString(); System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); System.out.println(userName); System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"); AtomicInteger retryCount = passwordRetryCache.get(userName); if (null == retryCount) { retryCount = new AtomicInteger(0); passwordRetryCache.put(userName,retryCount); } if (retryCount.incrementAndGet() > 5) { throw new ExcessiveAttemptsException(); } boolean matches = super.doCredentialsMatch(token, info); if (matches){ passwordRetryCache.remove(userName); } return matches; } }
spring-shiro.xml內加入配置數據庫
<!-- 數據庫保存的密碼是使用MD5算法加密的,因此這裏須要配置一個密碼匹配對象 --> <!--<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.Md5CredentialsMatcher"></bean>--> <bean id="credentialsMatcher" class="com.ken.shiro.RetryLimitHashedCredentialsMatcher"> <constructor-arg ref="cacheManager"></constructor-arg> <property name="hashAlgorithmName" value="md5"></property><!--加密算法爲md5--> <property name="hashIterations" value="3"></property><!--3次md5迭代--> <!--是否存儲散列後的密碼爲16進制,須要和生成密碼時的同樣,默認是base64--> <property name="storedCredentialsHexEncoded" value="true"></property> </bean>
<!--緩存管理-->
<!--<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.MemoryConstrainedCacheManager"></bean>-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" >
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property>
</bean>
ehcache.xml的配置以下:apache
<?xml version="1.0" encoding="UTF-8"?> <ehcache name="shirocache"> <diskStore path="java.io.tmpdir" /> <!-- name:緩存名稱。 maxElementsInMemory:緩存最大個數。 eternal:對象是否永久有效,一但設置了,timeout將不起做用。 timeToIdleSeconds:設置對象在失效前的容許閒置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閒置時間無窮大。 timeToLiveSeconds:設置對象在失效前容許存活時間(單位:秒)。最大時間介於建立時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,默認是0.,也就是對象存活時間無窮大。 overflowToDisk:當內存中對象數量達到maxElementsInMemory時,Ehcache將會對象寫到磁盤中。 diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每一個Cache都應該有本身的一個緩衝區。 maxElementsOnDisk:硬盤最大緩存個數。 diskPersistent:是否緩存虛擬機重啓期數據 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。 memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你能夠設置爲FIFO(先進先出)或是LFU(較少使用)。 clearOnFlush:內存數量最大時是否清除。 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> <!-- 登陸記錄緩存 鎖定10分鐘 --> <cache name="passwordRetryCache" eternal="false" maxElementsInMemory="0" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> <cache name="authorizationCache" eternal="false" maxElementsInMemory="0" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> <cache name="authenticationCache" eternal="false" maxElementsInMemory="0" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> <cache name="shiro-activeSessionCache" eternal="false" maxElementsInMemory="0" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> </ehcache>
實現Realm類MyShiro繼承自AuthorizingRealm,AuthorizingRealm實現它的抽象方法doGetAuthorizationInfo權限角色進行配置,AuthorizingRealm又繼承自AuthenticatingRealm,AuthenticatingRealm也有一個抽象方法doGetAuthenticationInfo,實現doGetAuthenticationInfo方法對登陸的令牌等信息進行驗證。緩存
myShiro的職則是對登陸進行受權,對角色、權限進行驗證等。session
package com.ken.service.impl; import com.ken.entity.TRole; import com.ken.entity.TUser; import com.ken.service.IUserService; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authc.credential.CredentialsMatcher; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.session.Session; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class MyShiro extends AuthorizingRealm { @Autowired IUserService userService; @Autowired //注入父類的屬性,注入加密算法匹配密碼時使用 public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher){ super.setCredentialsMatcher(credentialsMatcher); } protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("######################"); System.out.println("realm==="+this.getName()); System.out.println("######################"); //獲取登陸時輸入的用戶名 String loginName = (String)principalCollection.fromRealm(getName()).iterator().next(); //獲取當前的用戶名,跟上面的同樣 String currentUsername = (String)super.getAvailablePrincipal(principalCollection); System.out.println(currentUsername); TUser user = userService.findUserByName(loginName); if (user != null) { //權限信息對象info,用來存放查出的用戶的全部的角色(role)及權限(permission) SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //用戶的角色集合 simpleAuthorizationInfo.setRoles(user.getRolesName()); //對應角色的權限 List<TRole> roles = user.getRoles(); for (TRole role:roles){ simpleAuthorizationInfo.addStringPermissions(role.getPermissionName()); } return simpleAuthorizationInfo; } return null; } //若是驗證成功,將返回AuthenticationInfo驗證信息;此信息中包含了身份及憑證;若是驗證失敗將拋出相應的AuthenticationException實現。 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; System.out.println("AuthenticationInfo####################"); System.out.println(((UsernamePasswordToken) authenticationToken).getUsername()); String pwd = new String(((UsernamePasswordToken) authenticationToken).getPassword()); System.out.println("getPassword="+pwd); System.out.println(authenticationToken.getPrincipal().toString()); String password = new String((char[])authenticationToken.getCredentials()); //獲得密碼 System.out.println(password); this.setSession("currentUser",authenticationToken.getPrincipal().toString()); TUser user = userService.findUserByName(token.getUsername()); if (user != null) { System.out.println("user salt is "+user.getCredentialsSalt()); //這裏獲取到數據庫的用戶名密碼,而後驗證用戶名密碼,若是不對則執出異常 return new SimpleAuthenticationInfo(user.getUserName(),user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()) //獲取鹽 ,getName()); } return null; } /** * 將一些數據放到ShiroSession中,以便於其它地方使用 * 好比Controller,使用時直接用HttpSession.getAttribute(key)就能夠取到 */ private void setSession(Object key,Object value){ Subject subject = SecurityUtils.getSubject(); if (null != subject) { Session session = subject.getSession(); if (null != session) { session.setAttribute(key,value); } } } }
完整的spring-shiro.xml文件以下:框架
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置權限管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- ref對應咱們寫的realm MyShiro --> <property name="realm" ref="myShiro"></property> <property name="cacheManager" ref="cacheManager"></property> </bean> <!-- 配置shiro的過濾器工廠類,id- shiroFilter要和咱們在web.xml中配置的過濾器一致 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 調用咱們配置的權限管理器 --> <property name="securityManager" ref="securityManager"></property> <property name="loginUrl" value="/login"></property> <!-- 配置咱們在登陸頁登陸成功後的跳轉地址,若是你訪問的是非/login地址,則跳到您訪問的地址 --> <property name="successUrl" value="/user"></property> <!-- 若是您請求的資源再也不您的權限範圍,則跳轉到/403請求地址 --> <property name="unauthorizedUrl" value="/403"></property> <!-- 權限配置 --> <property name="filterChainDefinitions"> <value> <!-- anon表示此地址不須要任何權限便可訪問 refer to:http://blog.csdn.net/jadyer/article/details/12172839 --> /static/**=anon /verifyImage=anon <!-- perms[user:query]表示訪問此鏈接須要權限爲user:query的用戶 --> /user=perms[query] <!-- roles[manager]表示訪問此鏈接須要用戶的角色爲manager --> /user/add=roles[manager] /user/del/**=roles[admin] /user/edit/**=roles[manager] <!--全部的請求(除去配置的靜態資源請求或請求地址爲anon的請求)都要經過登陸驗證,若是未登陸則跳到/login--> /** = authc </value> </property> </bean> <!-- 數據庫保存的密碼是使用MD5算法加密的,因此這裏須要配置一個密碼匹配對象 --> <!--<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.Md5CredentialsMatcher"></bean>--> <bean id="credentialsMatcher" class="com.ken.shiro.RetryLimitHashedCredentialsMatcher"> <constructor-arg ref="cacheManager"></constructor-arg> <property name="hashAlgorithmName" value="md5"></property><!--加密算法爲md5--> <property name="hashIterations" value="3"></property><!--3次md5迭代--> <!--是否存儲散列後的密碼爲16進制,須要和生成密碼時的同樣,默認是base64--> <property name="storedCredentialsHexEncoded" value="true"></property> </bean> <!-- 配置 Bean 後置處理器: 會自動的調用和 Spring 整合後各個組件的生命週期方法. --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean> <!--緩存管理--> <!--<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.MemoryConstrainedCacheManager"></bean>--> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager" > <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property> </bean> </beans>