Authentication(身份認證)是Shiro權限控制的第一步,用來告訴系統你就是你。
在提交認證的時候,我們需要給系統提交兩個信息:
Principals:是一個表示用戶的唯一屬性,可以是用戶名,郵箱之類的。
Credentials:是證明用戶身份的證書,可以是密碼或者指紋之類的。
認證主要分為三步:
1、收集認證信息
2、提交認證信息
3、如果認證成功,則允許訪問,否則就拒絕訪問或者重試。
收集認證信息:
在入門的例子中,使用了一個UsernamePasswordToken來收集用戶的用戶名和密碼,用來登陸。
這個類支持最簡單的用戶名和密碼登陸。實現了org.apache.shiro.authc.AuthenticationToken接口。
AuthenticationToken接口是認證系統的基礎,只有getCredentials(),getPrincipal()兩個方法用來獲取基本的認證信息。
HostAuthenticationToken和RememberMeAuthenticationToken是它的兩個子接口。
HostAuthenticationToken只有一個getHost()方法用來獲取請求的地址信息。
RememberMeAuthenticationToken只有一個isRememberMe()方法用來標記用戶是否需要記住我。
然后還有兩個子類UsernamePasswordToken和CasToken提供了基本的實現。其中CasToken已經被廢棄了。
例:
//Example using most common scenario of username/password pair: UsernamePasswordToken token = new UsernamePasswordToken(username, password); //"Remember Me" built-in: token.setRememberMe(true);
提交認證信息:
收集好認證信息之后,保存為AuthenticationToken的一個實例,我們需要提交這個認證信息來進行認證。
進行認證前,我們首先需要獲取當前用戶(Subject)。然后調用login方法來進行登陸。
例:
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
處理結果:
如果登陸成功的話,則不會有什么異常,此時如果調用isAuthenticated()方法,則會返回true。
如果登陸失敗的話,則被拋出異常,在SHiro中,提供了很多異常類,可以用來捕捉具體的異常。
例子:
try { currentUser.login(token); } catch ( UnknownAccountException uae ) { ... } catch ( IncorrectCredentialsException ice ) { ... } catch ( LockedAccountException lae ) { ... } catch ( ExcessiveAttemptsException eae ) { ... } ... catch your own ... } catch ( AuthenticationException ae ) { //unexpected error? } //No problems, continue on as expected...
可以看到,前面出現了Remembered和Authenticated。那這兩種有什么區別呢?
需要注意的是,這是兩種互斥的情況,當我們在登陸的時候,登陸成功之后,我們Authenticated返回的是true。
選擇Remeber me的時候,我們下次可以不登陸直接訪問,而我們下次登陸之后,Remembered返回的是true,但是Authenticated返回的是false。
在登陸之后,我們可以可以用logout()方法來注銷。這時候用戶的信息都會被清空,包括保存在Cookie中的RemeberMe信息還有session也會被無效。
上面我們就簡單的講述了一下在代碼中實現登陸驗證的流程。具體實例可以參考入門的例子。
那么,在Shrio內部是怎么樣的一個認證流程呢?大概可以用下圖來概括:

第一步:在代碼中調用login方法,傳遞構造好的AuthenticationToken實例。
第二步:通過一個DelegatingSubject來分發認證請求給SecurityManager
第三步:SecurityManager容器會把接收到的token簡單的轉發給它內部的認證器實例通過調用認證器的authenticate(token)方法。
這通常是一個ModularRealmAuthenticator實例,用來支持多個Realm。
第四步:如果有定義多個Realm則ModularRealmAuthenticator會初始化一個支持多個Realm的認證器,通過配置的AuthenticationStrategy。
第五步:每一個配置的Realm都會被檢測是否支持提交的AuthenticationToken,如果支持的話就會調用getAuthenticationInfo方法,從Realm中獲取數據來跟提交的token進行驗證。
驗證器
在SecurityManager中,默認使用ModularRealmAuthenticator實例,它不僅僅支持單Realm還支持多個Realm。
如果實在單個Realm的情況下,ModualrRealmAuthenticator會直接調用這個Realm來嘗試驗證。
如果我們需要定義自己的驗證器的話,可以通過在配置文件的[main]中如下定義:
[main] ... authenticator = com.foo.bar.CustomAuthenticator securityManager.authenticator = $authenticator
驗證策略
如果是只有一個Realms的時候,則不需要驗證策略。
如果是有兩個以上的Realm的時候,ModularRealmAuthenticator依賴於AuthenticationStrategy組件來決定認證成功或者失敗的條件,
比如是一個成功就成功還是要都成功才是成功之類的。。。。
在Shiro里面已經有三個認證策略的實現
AtLeastOneSuccessfulStrategy:只要有一個或一個以上的認證成功就表示認證成功
FirstSuccessfulStrategy;只有第一個認證成功的時候才表示認證成功
AllSuccessfulStrategy:只有所有的都認證成功的時候才表示認證成功。
在ModularRealmAuthenticator默認使用AtLeastOneSuccessfulStrategy的驗證策略。當然也可以通過下面的方式定義其他的驗證策略。
[main] ... authcStrategy = org.apache.shiro.authc.pam.FirstSuccessfulStrategy securityManager.authenticator.authenticationStrategy = $authcStrategy ...
Realm認證順序
在定義了多個Realm的時候,如果有多個Realm支持當前的AuthenticationToken,則會依次調用Realm的getAuthenticationInfo方法。
調用的順序分為兩種:
隱式的:
如果在SecurityManager中定義了如下的幾個Realm,則會按照他們定義的順序去調用。
blahRealm = com.company.blah.Realm ... fooRealm = com.company.foo.Realm ... barRealm = com.company.another.Realm
此時的效果就等於下面的語句。
securityManager.realms = $blahRealm, $fooRealm, $barRealm、
顯示的:
就如上面的一樣,如果在securityManager.realms中配置的時候,改變realm的配置順序,則會按照這個配置順序來調用,這就是顯示的配置。
blahRealm = com.company.blah.Realm ... fooRealm = com.company.foo.Realm ... barRealm = com.company.another.Realm securityManager.realms = $fooRealm, $barRealm, $blahRealm ...
域認證
前面說了下Realm的認證策略和認證順序,那么,在Realm認證的時候,究竟發生什么事情了呢?
supporting AuthenticationTokens
在Realm被用來嘗試認證登陸的時候首先會調用supports方法,來檢測是否能解析這個AuthenticationToken,決定是否進行認證。
Handing supported AuthenticationTokens
如果這個realm支持提交的AuthenticationTokens的話,解析器會調用Realm的getAuthenticationINfo(token)方法,來嘗試認證,
這個方法大概做了下面這些事情:
1、檢測token中的唯一用戶標識
2、基於唯一標識 去數據源中查找
3、確保提供的證書跟數據源中保存的一樣
4、如果證書一樣,就封裝一個AuthenticationINfo實例返回
5、如果證書不匹配,則拋出一個AuthenticationException異常
下面,我們來看看Shrio提供的Realm的類結構:
org.apache.shiro.realm.Realm(I):base
org.apache.shiro.realm.CachingRealm(Abstract):提供緩存支持
org.apache.shiro.realm.AuthenticatingRealm(Abstract):提供認證支持
org.apache.shiro.realm.AuthorizingRealm(Abstract):提供授權支持
org.apache.shiro.realm.SimpleAccountRealm(C):簡單的用戶名密碼支持
org.apache.shiro.realm.text.TextConfigurationRealm(C):支持Text文件的簡單用戶名密碼支持
org.apache.shiro.realm.text.IniRealm(C):通過INI文件的簡單用戶名密碼支持(默認使用這個)
org.apache.shiro.realm.text.PropertiesRealm(C):支持屬性文件的簡單用戶名密碼支持
org.apache.shiro.realm.jdbc.JdbcRealm(C):支持通過JDBC認證
在系統默認的情況下,系統使用的是IniRealm這個實現,可以從INI配置文件的[users]和[roles]兩個節中讀取用戶信息和權限信息。
如果我們需要定義自己的Realm實現的話,一般都是繼承AuthorizingRealm。
稍候,我們將簡單介紹下如果從db中來實現認證。
證書匹配:
前面我們說過,Realm需要去匹配用戶提交的證書跟數據源中存儲的證書是否匹配,如果匹配的話就認為是認證成功。
在獲得用戶唯一標識后,系統回去Realm會去檢索用戶的證書,然后通過CredentialsMatcher來檢測證書是否匹配。
Shrio中也提供了一些證書的匹配器可以直接拿來使用,使用下面的方法變更默認的匹配器:
[main] ... customMatcher = com.company.shiro.realm.CustomCredentialsMatcher myRealm = com.company.shiro.realm.MyRealm myRealm.credentialsMatcher = $customMatcher ...
Simple Equality Check
默認情況下所有提供的Realm實現都是使用SimpleCredentialsMatcher來檢測證書是否匹配。
不過一般情況下都不需要變更,因為默認的就足夠了。
Hashing Credentials
相比使用原始的數據存儲起來,拿來匹配,我們更願意將證書加密進行存儲來進行匹配。那么如何使用呢?
在Shiro中提供了幾個HashedCredentialsMatcher的子類,用來實現這個功能。包括MD5、SHA-256等等的加密方式。
我們可以通過下面的配置方式:
[main] ... credentialsMatcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher # base64 encoding, not hex in this example: credentialsMatcher.storedCredentialsHexEncoded = false credentialsMatcher.hashIterations = 1024 # This next property is only needed in Shiro 1.0\. Remove it in 1.1 and later: credentialsMatcher.hashSalted = true ... myRealm = com.company..... myRealm.credentialsMatcher = $credentialsMatcher ...
需要注意的是,這種情況下在Realm的實現中需要返回一個SaltedAuthenticationInfo,而不是普通的AuthenticationInfo,因為在用戶提交認證的時候,需要獲取相同的salt來進行加密,進行匹配認證。
那這個salt(鹽)是用來干嘛的呢?
這是因為在進行MD5之類加密的時候,還是可以進行破解的,但是如果加入一個變量來進行加密之后,就基本上是無法破解了(不知道這個SALT的情況下)。
下面我們就做一個通過JDBC來認證的登陸認證程序。點此看源碼
首先我們需要一個用戶表,腳本如下:
CREATE DATABASE `db_shiro` USE `db_shiro`; DROP TABLE IF EXISTS `users`; CREATE TABLE `users` ( `id` int(4) NOT NULL AUTO_INCREMENT, `username` varchar(20) DEFAULT NULL, `password` varchar(100) DEFAULT NULL, UNIQUE KEY `id` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; insert into `users`(`id`,`username`,`password`) values (1,'fuwh','123456');
我們需要定義一個shiro_jdbc.ini文件如下
[main] dataSource=com.mchange.v2.c3p0.ComboPooledDataSource dataSource.driverClass=com.mysql.jdbc.Driver dataSource.jdbcUrl=jdbc:mysql://localhost:3306/db_shiro dataSource.user=root dataSource.password=rootadmin ;this is comment read from mysql jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm jdbcRealm.dataSource=$dataSource securityManager.realms=$jdbcRealm
編寫登陸認證代碼:
package com.fuwh.demo; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ShiroDemo02 { private static Logger log=LoggerFactory.getLogger(ShiroDemo02.class); public static void main(String[] args) { //取得SecurityManager工廠 Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro_jdbc.ini"); //取得SecurityManager實例 SecurityManager securityManager=factory.getInstance(); //將securityManager綁定到SecurityUtil SecurityUtils.setSecurityManager(securityManager); /* 至此為止,簡單的從mysql數據庫讀取realm信息的shiro環境就配置好了 */ //取得當前用戶 Subject currentUser=SecurityUtils.getSubject(); //使用shiro來進行登陸驗證 if(!currentUser.isAuthenticated()) { UsernamePasswordToken token=new UsernamePasswordToken("fuwh","123456"); try { currentUser.login(token); log.info("登陸成功!!!"); } catch (Exception e) { e.printStackTrace(); log.error("認證失敗..."); } } currentUser.logout(); } }
執行結果:
2017-08-26 15:16:03,357 [main] INFO [com.mchange.v2.log.MLog] - MLog clients using log4j logging. 2017-08-26 15:16:04,107 [main] INFO [com.mchange.v2.c3p0.C3P0Registry] - Initializing c3p0-0.9.1.2 [built 21-May-2007 15:04:56; debug? true; trace: 10] 2017-08-26 15:16:04,436 [main] INFO [org.apache.shiro.config.IniSecurityManagerFactory] - Realms have been explicitly set on the SecurityManager instance - auto-setting of realms will not occur. 2017-08-26 15:16:04,560 [main] INFO [com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource] - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1hgetj59q83i1dm1es8ork|67f89fa3, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hgetj59q83i1dm1es8ork|67f89fa3, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/db_shiro, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ] 2017-08-26 15:16:05,040 [main] INFO [org.apache.shiro.session.mgt.AbstractValidatingSessionManager] - Enabling session validation scheduler... 2017-08-26 15:16:05,056 [main] INFO [com.fuwh.demo.ShiroDemo02] - 登陸成功!!!
此時,已經可以從表中去驗證登陸了。那處理流程又是什么樣的呢?
首先我們使用shiro_jdbc.ini來初始化了SecurityManager,在配置文件中,我們定義了連接池信息,還有jdbcRealm,同時在SecurityManager中指定了realm為定義的JjdbcRealm,這時候,其實shiro使用的SecurityManager是一個RealmSecurityManager的實例。而當我們登陸的時候,則會通過配置的jdbcRealm來從數據庫中取得用戶信息來進行認證。那是怎么取得呢?我們明明沒有寫sql什么的。
其實,看org.apache.shiro.realm.jdbc.JdbcRealm的源碼可以看到,在這個類里面定義了很多的靜態sql變量,點此查看,點此查看sql內容
其中比較重要的是AuthenticationQuery這個字段,默認情況下它的值是等於Default_Authentication_query。
Default_Authentication_Query="select password from users where username=?";
所以在默認情況下,它會從users這個表中通過username這個key來查找password。從而拿來跟我們提交的密碼來進行匹配驗證。
如果我們不想使用默認的數據庫,默認的表名,默認的列名的話,也可以通過在配置文件中重寫AuthenticationQuery的值來個性化sql文。
首先修改新建一個表members:
USE `db_shiro`; DROP TABLE IF EXISTS `members`; CREATE TABLE `members` ( `id` INT(4) NOT NULL AUTO_INCREMENT, `userName` VARCHAR(20) DEFAULT NULL, `pass` VARCHAR(100) DEFAULT NULL, UNIQUE KEY `id` (`id`) ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO `members`(`id`,`userName`,`pass`) VALUES (1,'fuwh','123');
然后修改shiro_jdbc_sql.ini配置文件:
[main] dataSource=com.mchange.v2.c3p0.ComboPooledDataSource dataSource.driverClass=com.mysql.jdbc.Driver dataSource.jdbcUrl=jdbc:mysql://localhost:3306/db_shiro dataSource.user=root dataSource.password=rootadmin ;this is comment read from mysql jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm jdbcRealm.dataSource=$dataSource jdbcRealm.authenticationQuery=select pass from members where userName=? securityManager.realms=$jdbcRealm
修改認證程序的配置文件:
package com.fuwh.demo; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ShiroDemoSql02 { private static Logger log=LoggerFactory.getLogger(ShiroDemoSql02.class); public static void main(String[] args) { //取得SecurityManager工廠 Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro_jdbc_sql.ini"); //取得SecurityManager實例 SecurityManager securityManager=factory.getInstance(); //將securityManager綁定到SecurityUtil SecurityUtils.setSecurityManager(securityManager); /* 至此為止,簡單的從mysql數據庫讀取realm信息的shiro環境就配置好了 */ //取得當前用戶 Subject currentUser=SecurityUtils.getSubject(); //使用shiro來進行登陸驗證 if(!currentUser.isAuthenticated()) { UsernamePasswordToken token=new UsernamePasswordToken("fuwh","123"); try { currentUser.login(token); log.info("登陸成功!!!"); } catch (Exception e) { e.printStackTrace(); log.error("認證失敗..."); } } currentUser.logout(); } }
后面我們會講到角色認證也是同樣的道理。
自定義Realm
上面我們使用的是Shiro提供的默認的Realm,下面我們自定義一個從數據庫中讀取信息的Realm,通過繼承AuthorizingRealm。
package com.fuwh.realm; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import com.fuwh.util.DbUtil; public class MyJdbcRealm extends AuthorizingRealm{ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // TODO Auto-generated method stub return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // TODO Auto-generated method stub Connection conn=DbUtil.getConnection(); String sql="select * from members2 where username=?"; try { PreparedStatement ps=conn.prepareStatement(sql); ps.setString(1, token.getPrincipal().toString()); ResultSet rs=ps.executeQuery(); while(rs.next()) { AuthenticationInfo info=new SimpleAuthenticationInfo(rs.getString("username"),rs.getString("password"),"salt"); return info; } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }
修改配置文件
[main] myJdbcRealm=com.fuwh.realm.MyJdbcRealm securityManager.realms=$myJdbcRealm
編寫登陸類:
package com.fuwh.demo; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ShiroDemoMySql02 { private static Logger log=LoggerFactory.getLogger(ShiroDemoMySql02.class); public static void main(String[] args) { //取得SecurityManager工廠 Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro_jdbc_my_sql.ini"); //取得SecurityManager實例 SecurityManager securityManager=factory.getInstance(); //將securityManager綁定到SecurityUtil SecurityUtils.setSecurityManager(securityManager); /* 至此為止,簡單的從mysql數據庫讀取realm信息的shiro環境就配置好了 */ //取得當前用戶 Subject currentUser=SecurityUtils.getSubject(); //使用shiro來進行登陸驗證 if(!currentUser.isAuthenticated()) { UsernamePasswordToken token=new UsernamePasswordToken("fuwh","123"); try { currentUser.login(token); log.info("登陸成功!!!"); } catch (Exception e) { e.printStackTrace(); log.error("認證失敗..."); } } currentUser.logout(); } }
那如果我們定義了多個Realm呢?
在編寫一個Realm類:
package com.fuwh.realm; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; public class MyJdbcRealm2 extends AuthorizingRealm{ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // TODO Auto-generated method stub return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // TODO Auto-generated method stub return new SimpleAuthenticationInfo(token.getPrincipal(),"1234","salt"); } }
修改shiro_jdbc_my_sql_2.ini配置文件:
[main] myJdbcRealm=com.fuwh.realm.MyJdbcRealm myJdbcRealm2=com.fuwh.realm.MyJdbcRealm2 authStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy securityManager.realms=$myJdbcRealm,$myJdbcRealm2 securityManager.authenticator.authenticationStrategy=$authStrategy
修改登陸類:
package com.fuwh.demo; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ShiroDemoMySql02_2 { private static Logger log=LoggerFactory.getLogger(ShiroDemoMySql02_2.class); public static void main(String[] args) { //取得SecurityManager工廠 Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro_jdbc_my_sql_2.ini"); //取得SecurityManager實例 SecurityManager securityManager=factory.getInstance(); //將securityManager綁定到SecurityUtil SecurityUtils.setSecurityManager(securityManager); /* 至此為止,簡單的從mysql數據庫讀取realm信息的shiro環境就配置好了 */ //取得當前用戶 Subject currentUser=SecurityUtils.getSubject(); //使用shiro來進行登陸驗證 if(!currentUser.isAuthenticated()) { UsernamePasswordToken token=new UsernamePasswordToken("fuwh","123"); try { currentUser.login(token); log.info("登陸成功!!!"); } catch (Exception e) { e.printStackTrace(); log.error("認證失敗..."); } } currentUser.logout(); } }
上面的例子中使用的認證策略是只有一個成功就認為是成功。
在Shiro中還提供了下面幾個策略:
FirstSuccessfulStrategy:只有第一個認證成功的信息會被用
AllSuccessfulStrategy:只有當所有的都成功的時候才認為是認證成功。
源碼地址:https://github.com/oukafu/shiro
