在流程图3中,有Authenticator和AuthenticationStrategy2个接口。
Authenticator的职责是验证用户帐号,是Shiro API中身份验证核心的入口点:
它只有一个方法:
public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)
throws AuthenticationException;
如果验证成功,将返回 AuthenticationInfo 验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的 AuthenticationException 实现。
选中AuthenticationInfo,按住Ctrl+t,可以看到它的继承体系。
SecurityManager接口继承了Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm进行验证,验证规则通过AuthenticationStrategy接口指定,默认提供的实现:
FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略;
AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,返回所有Realm身份验证成功的认证信息;
AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
ModularRealmAuthenticator默认使用AtLeastOneSuccessfulStrategy策略。
一个例子:
假设我们有三个realm:
myRealm1: 用户名/密码为zhang/123时成功,且返回身份/凭据为zhang/123;
myRealm2: 用户名/密码为wang/123时成功,且返回身份/凭据为wang/123;
myRealm3: 用户名/密码为zhang/123时成功,且返回身份/凭据为zhang@163.com/123,和myRealm1不同的是返回时的身份变了;
三个reaml文件如下:
package com.lgy.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.realm.Realm;
public class MyRealm1 implements Realm {
public String getName() {
return "myrealm1";
}
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken; //仅支持UsernamePasswordToken类型的Token
}
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal(); //得到用户名
String password = new String((char[])token.getCredentials()); //得到密码
if(!"zhang".equals(username)) {
throw new UnknownAccountException(); //如果用户名错误
}
if(!"123".equals(password)) {
throw new IncorrectCredentialsException(); //如果密码错误
}
//如果身份认证验证成功,返回一个AuthenticationInfo实现;
return new SimpleAuthenticationInfo(username, password, getName());
}
}
package com.lgy.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.realm.Realm;
public class MyRealm2 implements Realm {
public String getName() {
return "myrealm2";
}
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken; //仅支持UsernamePasswordToken类型的Token
}
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal(); //得到用户名
String password = new String((char[])token.getCredentials()); //得到密码
if(!"wang".equals(username)) {
throw new UnknownAccountException(); //如果用户名错误
}
if(!"123".equals(password)) {
throw new IncorrectCredentialsException(); //如果密码错误
}
//如果身份认证验证成功,返回一个AuthenticationInfo实现;
return new SimpleAuthenticationInfo(username, password, getName());
}
}
package com.lgy.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.realm.Realm;
public class MyRealm3 implements Realm {
public String getName() {
return "myrealm3";
}
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken; //仅支持UsernamePasswordToken类型的Token
}
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal(); //得到用户名
String password = new String((char[])token.getCredentials()); //得到密码
if(!"zhang".equals(username)) {
throw new UnknownAccountException(); //如果用户名错误
}
if(!"123".equals(password)) {
throw new IncorrectCredentialsException(); //如果密码错误
}
//如果身份认证验证成功,返回一个AuthenticationInfo实现;
return new SimpleAuthenticationInfo(username + "163@qq.com", password, getName());
}
}
private void login(String configFile) {
//1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
Factory<org.apache.shiro.mgt.SecurityManager> factory =
new IniSecurityManagerFactory(configFile);
//2、得到SecurityManager实例 并绑定给SecurityUtils
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
//3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
subject.login(token);
}
@After
public void tearDown() throws Exception {
ThreadContext.unbindSubject();//退出时请解除绑定Subject到线程 否则对下次测试造成影响
}
上面说的:
默认策越(ModularRealmAuthenticator默认使用AtLeastOneSuccessfulStrategy策略。)DEMO
ini文件:
[main]
#声明一个realm
myRealm1=com.lgy.shiro.MyRealm1
myRealm2=com.lgy.shiro.MyRealm2
myRealm3=com.lgy.shiro.MyRealm3
#指定securityManager的realms实现
securityManager.realms=$myRealm1,$myRealm2,$myRealm3
测试如下:
@Test
public void testStrategyWithdefault() {
login("classpath:shiro-auth-default.ini");
// login("classpath:shiro-realm.ini");
Subject subject = SecurityUtils.getSubject();
/*
boolean b = subject.isAuthenticated();
System.out.println(b);*/
//得到一个身份集合,其包含了Realm验证成功的身份信息
PrincipalCollection principalCollection = subject.getPrincipals();
System.out.println(principalCollection.asList().size()); //返回所有的身份凭证信息
//Assert.assertEquals(2, principalCollection.asList().size());
}
验证信息是zhang/123, myRealm1和myRealm2是能通过的,打印返回的size为2,正如前面所说的:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,返回所有Realm身份验证成功的认证信息;
FirstSuccessfulStrategy策越,demo如下:
ini文件:
[main]
#指定securityManager的authenticator实现
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator
#指定securityManager.authenticator的authenticationStrategy
firstSuccessfulStrategy=org.apache.shiro.authc.pam.FirstSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$firstSuccessfulStrategy
#声明一个realm
myRealm1=com.lgy.shiro.MyRealm1
myRealm2=com.lgy.shiro.MyRealm2
myRealm3=com.lgy.shiro.MyRealm3
#指定securityManager的realms实现
securityManager.realms=$myRealm1,$myRealm2,$myRealm3
测试代码:
@Test
public void testStrategyWithdefirst() {
login("classpath:shiro-auth-first.ini");
Subject subject = SecurityUtils.getSubject();
System.out.println(subject.isAuthenticated());
PrincipalCollection principalCollection = subject.getPrincipals();
System.out.println(principalCollection.asList().size());
}
验证信息是zhang/123, myRealm1和myRealm2是能通过的,打印返回的size为1,正如前面所说的只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略;
AllSuccessfulStrategy测试,demo如下:
ini文件:
[main]
#指定securityManager的authenticator实现
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator
#指定securityManager.authenticator的authenticationStrategy
allSuccessfulStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy
#声明一个realm
myRealm1=com.lgy.shiro.MyRealm1
myRealm2=com.lgy.shiro.MyRealm2
myRealm3=com.lgy.shiro.MyRealm3
#指定securityManager的realms实现
securityManager.realms=$myRealm1,$myRealm2,$myRealm3
测试文件:
@Test
public void testStrategyWithdeAll() {
login("classpath:shiro-auth-all.ini");
Subject subject = SecurityUtils.getSubject();
System.out.println(subject.isAuthenticated());
PrincipalCollection principalCollection = subject.getPrincipals();
System.out.println(principalCollection.asList().size());
}
验证信息是zhang/123, myRealm1和myRealm3是能通过的,但是myRealm2不是通过,所以会抛出异常,测试部通过。 将myRealm2中的wang改为zhang,所以验证能通过,打印为2,为什么?因为返回凭证中myRealm1和myRealm2一样被当做同一个凭证。