Authenticator及AuthenticationStrategy
Authenticator的职责是验证用户,被subject的login()调用。
public AuthenticationInfo authenticate(AuthenticationToken authenticationToken) throws AuthenticationException { //调用了Realm的getAuthenticationInfo() }
- Authenticator:SecurityManager是其子接口,默认实现类为ModularRealmAuthenticator,其委托给多个Realm进行验证。
- AuthenticatorStrategy:制定多个Realm的验证规则。默认提供的实现:
- FirstSuccessfulStrategy:只要有一个Realm验证成功即可,值返回第一个验证成功的Realm的认证信息。
- AtLeastSuccessfulStrategy:只要有一个Realm验证成功即可,返回所有验证成功的Realm的认证信息。
- AllSuccessfulstrategy:默认使用;所有Realm都验证成功才行,返回所有认证信息。
- 假设我们有三个Realm:
- myRealm1: 用户名/密码为zhang/123时成功,且返回身份/凭据为zhang/123;
- myRealm2: 用户名/密码为wang/123时成功,且返回身份/凭据为wang/123;
- myRealm3: 用户名/密码为zhang/123时成功,且返回身份/凭据为zhang@163.com/123。
- 现在测试一下AllSuccessfulStrategy验证策略
首先是ini配置文件
;指定securityManager的authentication实现 authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator securityManager.authenticator=$authenticator ;指定securityManager.authenticator的authenticationStragegy allSuccessfulStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy myRealm1=com.haien.shiroHelloWorld.realm.MyRealm1 myRealm2=com.haien.shiroHelloWorld.realm.MyRealm2 myRealm3=com.haien.shiroHelloWorld.realm.MyRealm3 securityManager.realms=$myRealm1,$myRealm3
测试
/** * @Author haien * @Description 测试AllSuccessfulStrategy成功 * @Date 2019/2/16 * @Param [] * @return void **/ @Test public void testAllSuccessfulStrategyWithSuccess(){ //登录成功(因为ini配置文件中没有使用身份必须为wang的myRealm2) login("classpath:config/shiro-authenticator-all-success.ini"); //登录逻辑代码先通用化了 Subject subject=SecurityUtils.getSubject(); //得到一个身份集合,其包含了Realm验证成功的身份信息 PrincipalCollection principalCollection=subject.getPrincipals(); //包含了zhang和zhang@163.com的身份凭证 Assert.assertEquals(2,principalCollection.asList().size()); } /** * @Author haien * @Description 测试AllSuccessfulStrategy失败 * @Date 2019/2/17 * @Param [] * @return void **/ @Test(expected = UnknownAccountException.class) //括号里表明当方法抛出此异常时测试成功 public void testAllSuccessfulAccountWithFail(){ //但实际登录失败(配置文件改为使用myRealm1和myRealm2) login("classpath:config/shiro-authenticator-all-fail.ini"); Subject subject=SecurityUtils.getSubject(); }
- 以上测试失败方法如果捕获异常的话可获得以下调用链
自定义AuthenticationStrategy
AuthenticationStrategy接口类
//在所有Realm验证之前调用 AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException; //在每个Realm之前调用 AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException; //在每个Realm之后调用 AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException; //在所有Realm之后调用 AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate)throws AuthenticationException; //将所有验证成功的身份信息集合起来,便于最后一起返回;FirstSuccessfulStrategy类重写了该方法 AuthenticationInfo merge(AuthenticationInfo info, AuthenticationInfo aggregate)
自定义AuthenticationStrategy实现类一般继承AbstractAuthenticationStrategy即可。
AtLeastTwoAuthenticatorStrategy:至少两个通过才行,返回所有验证信息
public class AtLeastTwoAuthenticatorStrategy extends AbstractAuthenticationStrategy { /** * @Author haien * @Description 在所有Realm验证之前调用 * @Date 2019/2/17 * @Param [realms, token] * @return org.apache.shiro.authc.AuthenticationInfo **/ @Override public AuthenticationInfo beforeAllAttempts(Collection<? extends Realm> realms, AuthenticationToken token) throws AuthenticationException { //和父类方法一样,返回一个空的身份凭证 return new SimpleAuthenticationInfo(); } /** * @Author haien * @Description 在每个Realm验证之前调用 * @Date 2019/2/17 * @Param [realm, token, aggregate] * @return org.apache.shiro.authc.AuthenticationInfo **/ @Override public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException { //和父类方法一样,返回之前合并的 return aggregate; } @Override public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException { //也是和父类方法一样 AuthenticationInfo info; if(singleRealmInfo==null){ info=aggregateInfo; }else { if(aggregateInfo==null){ info=singleRealmInfo; }else{ //调用父类merge()将所有身份凭证集合起来,最后一起返回 info=merge(singleRealmInfo,aggregateInfo); } } return info; } /** * @Author haien * @Description 真正重写的方法:判断是否至少两个Realm通过 * @Date 2019/2/17 * @Param [token, aggregate] * @return org.apache.shiro.authc.AuthenticationInfo **/ @Override public AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException { if(aggregate==null||CollectionUtils.isEmpty(aggregate.getPrincipals()) ||aggregate.getPrincipals().getRealmNames().size()<2){ //是否至少两个通过 throw new AuthenticationException("Authentication token of type [" +token.getClass()+"]"+"could not be authenticated by any configured realms.Please ensure that at least two realm can authenticate these tokens."); } //这一步即父类方法体 return aggregate; } }
OnlyOneAuthenticatorStrategy:至少一个通过,重写的是afterAttempt()
@Override public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException { AuthenticationInfo info; if(singleRealmInfo == null){ //若当前身份凭证为空,说明未通过认证 info=aggregateInfo; //返回之前的身份凭证集合 }else{ //否则需要加入集合 if(aggregateInfo==null){ //若集合为空,则返回当前的身份凭证 info=singleRealmInfo; }else{ //两个都不为空才需要加入集合 info=merge(singleRealmInfo,aggregateInfo); if(info.getPrincipals().getRealmNames().size()>1){ System.out.println(info.getPrincipals().getRealmNames()); //打印集合 throw new AuthenticationException("Authentication token of type [" +token.getClass()+"]"+"could not be authenticated by any configured " + "realms. Please ensure that only one realm can authenticated " + "these tokens"); } } } return info; }
- 代码实例:ideaProjects/shiroHelloWord/authenticationstrategy
- 张开涛跟我学shiro第二章源码