眾所周知,在多個realm環境下:
AuthenticationStrategy是一個接口,同時它的實現類對象,對應ModularRealmAuthenticator里的屬性。
AuthenticationStrategy接口有三個實現類:
看一下各自得定義吧:
FirstSuccessfulStrategy:只要有一個Realm驗證成功即可,只返回第一個Realm身份驗證成功的認證信息,其他的忽略;(注:這里"第一個"指的是認證成功得那一個realm)
AtLeastOneSuccessfulStrategy:只要有一個Realm驗證成功即可,和FirstSuccessfulSTRATEGY不同,將返回所有Realm身份驗證成功的認證信息;
AllSuccessfulStrategy:所有Realm驗證成功過才算成功,且返回所有Realm身份驗證的認證信息,如果有一個失敗就失敗。
而shiro的默認認證策略是AtLeastOneSuccessfulStrategy。
還有其它兩個認證策略:
我想說:AtLeastOneSuccessfulStrategy為默認配置,AllSuccessfulStrategy配置也比較簡單。
但是在最新版本1.5.2版shiro中,FirstSuccessfulStrategy里新加了個屬性,這個屬性在shiro1.3.2中不存在。
這個屬性是——stopAfterFirstSuccess,(如果不是通過debug發現了這個屬性,我到現在也不會得到正確結論)。
shiro1.5.3源碼:
源碼位置:shiro-root-1.5.2\core\src\main\java\org\apache\shiro\authc\pam
FirstSuccessfulStrategy.java
package org.apache.shiro.authc.pam; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.realm.Realm; import org.apache.shiro.subject.PrincipalCollection; import java.util.Collection; /** * {@link AuthenticationStrategy} implementation that only accepts the account data from * the first successfully consulted Realm and ignores all subsequent realms. This is slightly * different behavior than {@link AtLeastOneSuccessfulStrategy}, so please review both to see * which one meets your needs better. * * @see AtLeastOneSuccessfulStrategy AtLeastOneSuccessfulAuthenticationStrategy * @since 0.9 */ public class FirstSuccessfulStrategy extends AbstractAuthenticationStrategy { private boolean stopAfterFirstSuccess; public void setStopAfterFirstSuccess (boolean stopAfterFirstSuccess ) { this.stopAfterFirstSuccess = stopAfterFirstSuccess ; } public boolean getStopAfterFirstSuccess() { return stopAfterFirstSuccess ; } /** * Returns {@code null} immediately, relying on this class's {@link #merge merge} implementation to return * only the first {@code info} object it encounters, ignoring all subsequent ones. */ public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException { return null; } /** * Throws ShortCircuitIterationException if stopAfterFirstSuccess is set and authentication is * successful with a previously consulted realm. * Returns the <code>aggregate</code> method argument, without modification * otherwise. */ public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException { if (getStopAfterFirstSuccess() && aggregate != null && !isEmpty(aggregate.getPrincipals())) { throw new ShortCircuitIterationException(); } return aggregate; } private static boolean isEmpty(PrincipalCollection pc) { return pc == null || pc.isEmpty(); } /** * Returns the specified {@code aggregate} instance if is non null and valid (that is, has principals and they are * not empty) immediately, or, if it is null or not valid, the {@code info} argument is returned instead. * <p/> * This logic ensures that the first valid info encountered is the one retained and all subsequent ones are ignored, * since this strategy mandates that only the info from the first successfully authenticated realm be used. */ protected AuthenticationInfo merge(AuthenticationInfo info, AuthenticationInfo aggregate) { if (aggregate != null && !isEmpty(aggregate.getPrincipals())) { return aggregate; } return info != null ? info : aggregate; } }
再看看1.3.2源碼:
源碼位置:shiro-root-1.3.2\shiro\core\src\main\java\org\apache\shiro\authc\pam
FirstSuccessfulStrategy.java
package org.apache.shiro.authc.pam; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.realm.Realm; import org.apache.shiro.util.CollectionUtils; import java.util.Collection; /** * {@link AuthenticationStrategy} implementation that only accepts the account data from * the first successfully consulted Realm and ignores all subsequent realms. This is slightly * different behavior than {@link AtLeastOneSuccessfulStrategy}, so please review both to see * which one meets your needs better. * * @see AtLeastOneSuccessfulStrategy AtLeastOneSuccessfulAuthenticationStrategy * @since 0.9 */ public class FirstSuccessfulStrategy extends AbstractAuthenticationStrategy { /** * Returns {@code null} immediately, relying on this class's {@link #merge merge} implementation to return * only the first {@code info} object it encounters, ignoring all subsequent ones. */ public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException { return null; } /** * Returns the specified {@code aggregate} instance if is non null and valid (that is, has principals and they are * not empty) immediately, or, if it is null or not valid, the {@code info} argument is returned instead. * <p/> * This logic ensures that the first valid info encountered is the one retained and all subsequent ones are ignored, * since this strategy mandates that only the info from the first successfully authenticated realm be used. */ protected AuthenticationInfo merge(AuthenticationInfo info, AuthenticationInfo aggregate) { if (aggregate != null && !CollectionUtils.isEmpty(aggregate.getPrincipals())) { return aggregate; } return info != null ? info : aggregate; } }
通過debug獲得的真相
之前的源碼關聯錯了(關聯1.3.2源碼,而我導入的pom依賴是1.5.2,因此我怎么也找不到這個屬性,后來不經意間想起來可能是源碼的問題)
不過當時雖然看不到這個屬性,但我還是知道它存在,那么我是怎么知道有它呢?
首先需寫配置文件:
shiro1.3.2在applicationContext.xml里配置多realm認證環境方式:(注:1.5.2版本如果這樣配置的話,將無法獲得FirstSuccessfulStrategy效果)
<!-- 多個realm時需要配置——ModularRealmAuthenticator--> <bean id ="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"> <property name="realms"> <list> <ref bean="jdbcRealm"/> <ref bean="jdbcRealm2"/> </list> </property> <property name="authenticationStrategy"> <bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"></bean> </property> </bean>
debug看一下(前提是配置了多個realm環境,並且可以debug到這一步):
可以看到該策略下(FirstSuccessfulStrategy),有一個stopAfterFirstSuccess屬性,默認是false。(我就是看到這里才發現這個屬性)。
默認false會是什么結果?
結果就是:
第一個realm即使驗證通過,它還是會去驗證第二個realm。
也就是說,當前的配置和AtLeastOneSuccessfulStrategy配置效果一樣,基本沒有變化。
可以看一下后台打印(兩realm都被認證):
代碼已經來到了:MyShiroRealm授權區域1-----> 通過realm-----沒有異常! 代碼已經來到了:MyShiroRealm授權區域2-----> 通過realm-----沒有異常!
那么如果把stopAfterFirstSuccess屬性改成true呢?
<!-- 多個realm時需要配置——ModularRealmAuthenticator--> <bean id ="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator"> <property name="realms"> <list> <ref bean="jdbcRealm"/> <ref bean="jdbcRealm2"/> </list> </property> <property name="authenticationStrategy"> <bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy"> <property name="stopAfterFirstSuccess" value="true"></property> </bean> </property> </bean>
再次debug:(觀察變化)
這次stopAfterFirstSuccess屬性的值為true,也就是說只要第一個realm通過認證,那么就不會匹配第二個realm了。
再看一下控制台打印,確實出現第一個通過認證的realm就不會接着認證(注:如果第一個realm沒有被認證就會接着認證其它realm,直到出現第一個通過認證的realm):
代碼已經來到了:MyShiroRealm授權區域1----->
通過realm-----沒有異常!
總結:shiro-1.5.2想要看到FirstSuccessfulStrategy真正的效果,需要在spring的bean里配置stopAfterFirstSuccess屬性為true,否則認證效果將等同於AtLeastOneSuccessfulStrategy。
(注:我猜想這樣設計的目的在於方便二者的切換,畢竟二者之間只相差這個屬性)
好啦,如果你讀懂了我的結論,那么將會解決你的一部分困惑。