Shiro-Authenticator
Shiro
的身份认证器(Authenticator
)在应用程序中负责验证用户账户信息,身份认证模块是Shiro
的主要模块之一,也是入口API之一。
下面,先看一下Authenticator
接口的定义:
public interface Authenticator {
public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)
throws AuthenticationException;
}
身份认证器接口的核心方法只有一个,即authenticate
方法。
- 此方法基于用户提交的
AuthenticationToken
来验证账户; - 如果验证成功,则返回一个表示
Shiro
有关账户数据的AuthenticationInfo
实例对象,返回的这个对象,通常用来构造一个Subject
对象,Subject
代表更加完整的安全性视图账户。有关AuthenticationInfo
的详细信息请参考:预留 - 如果验证失败,通常会抛出一个异常,不同的异常代表了不同的认证失败原因,常见的异常如下:
ExpiredCredentialsException
IncorrectCredentialsException
ExcessiveAttemptsException
LockedAccountException
ConcurrentAccessException
UnknownAccountException
UML 示例
有关Authenticator
的UML
示例如下:
从类图可以看到,Authenticator
的实现主要有两个,一个是ModularRealmAuthenticator
,另外一个是SecurityManager
。要知道,后者是Shiro
的核心控制器,类似Spring MVC
中的DispatcherServlet
一样。
这里,先不详细讲解SecurityManager
相关概念,我们直接来看一下DefaultSecurityManager
的认证是如何实现的:
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);
} catch (AuthenticationException ae) {
try {
onFailedLogin(token, ae, subject);
} catch (Exception e) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an " +
"exception. Logging and propagating original AuthenticationException.", e);
}
}
throw ae; //propagate
}
Subject loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
-
DefaultSecurityManager
的login
方法调用了authenticate
方法来进行身份认证。 -
authenticate
方法定义是在父抽象类AuthenticatingSecurityManager
中进行定义的。
AuthenticatingSecurityManager
public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {
/**
* The internal Authenticator
delegate instance that this SecurityManager instance will use
* to perform all authentication operations.
*/
private Authenticator authenticator;
/**
* Default no-arg constructor that initializes its internal
* authenticator
instance to a
* {@link org.apache.shiro.authc.pam.ModularRealmAuthenticator ModularRealmAuthenticator}.
*/
public AuthenticatingSecurityManager() {
super();
this.authenticator = new ModularRealmAuthenticator();
}
public Authenticator getAuthenticator() {
return authenticator;
}
/**
* Sets the delegate Authenticator
instance that this SecurityManager uses to perform all
* authentication operations. Unless overridden by this method, the default instance is a
* {@link org.apache.shiro.authc.pam.ModularRealmAuthenticator ModularRealmAuthenticator}.
*
*/
public void setAuthenticator(Authenticator authenticator) throws IllegalArgumentException {
if (authenticator == null) {
String msg = "Authenticator argument cannot be null.";
throw new IllegalArgumentException(msg);
}
this.authenticator = authenticator;
}
/**
* Delegates to the wrapped {@link org.apache.shiro.authc.Authenticator Authenticator} for authentication.
*/
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}
}
可以看到:
-
SecurityManager
并不会自己实现认证功能,而是保持有一个Authenticator
代理对象,使用此代理来实现认证验证功能。 -
SecurityManager
默认使用的身份认证器是ModularRealmAuthenticator
实例,我们可以通过setAuthenticator
方法重新指定一个。默认的指定是在默认构造函数里指定的:
public AuthenticatingSecurityManager() {
super();
this.authenticator = new ModularRealmAuthenticator();
}
- 由上述两点及UML类图得出结论,==
Shiro
中真正实现Authenticator
认证器接口的,只有一个实现类:ModularRealmAuthenticator
。Shiro
默认使用此实现来进行身份认证。==
ModularRealmAuthenticator
ModularRealmAuthenticator
本质上来讲,也并没有实现真正的认证功能,它保持有Reaml
s的引用,并把账户查找委托给Realm
来实现。而Realm
s是可插拔的模块化集合,这也使Shiro
中的PAM((Pluggable Authentication Module)行为成为可能 。
入口验证方法
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
- 首先确定
Realms
已经配置,如果没有配置,则验证失败 - 如果配置了1个
Reaml
,则调用单个Reaml
的验证方法,否则调用多个Reaml
验证方法。
单个验证方法源码:
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" +
token + "]. Please ensure that the appropriate Realm implementation is " +
"configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
}
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if (info == null) {
String msg = "Realm [" + realm + "] was unable to find account data for the " +
"submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
}
return info;
}
- 首先确定该
Realm
是否支持指定的token
验证方式,如果不支持,则验证失败。 - 调用
realm
的getAuthenticationInfo
方法,获取AuthenticationInfo
对象,如果获取为null,则验证失败。 - 返回验证成功后的
AuthenticationInfo
用户信息对象。
多个Reaml
验证
protected AuthenticationInfo doMultiRealmAuthentication(Collection realms, AuthenticationToken token) {
AuthenticationStrategy strategy = getAuthenticationStrategy();
AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
if (log.isTraceEnabled()) {
log.trace("Iterating through {} realms for PAM authentication", realms.size());
}
for (Realm realm : realms) {
aggregate = strategy.beforeAttempt(realm, token, aggregate);
if (realm.supports(token)) {
log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
AuthenticationInfo info = null;
Throwable t = null;
try {
info = realm.getAuthenticationInfo(token);
} catch (Throwable throwable) {
t = throwable;
if (log.isWarnEnabled()) {
String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
log.warn(msg, t);
}
}
aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
} else {
log.debug("Realm [{}] does not support token {}. Skipping realm.", realm, token);
}
}
aggregate = strategy.afterAllAttempts(token, aggregate);
return aggregate;
}
多个Realm
的认证机制涉及到一个策略的问题,Shiro
中的认证策略定义接口为:
AuthenticationStrategy
认证策略
Shiro
中主要有下面几种策略:
-
AllSuccessfulStrategy
: 所有的Reaml
都认证成功才算认证成功。 -
AtLeastOneSuccessfulStrategy
: 最少需要一个Realm
认证成功才算认证成功,此策略也是ModularRealmAuthenticator
的默认策略。 -
FirstSuccessfulStrategy
:第一个Realm
认证成功才算成功。
通过阅读策略源码就会发现,实现的方式只要是根据不同策略来决定是否抛出验证异常来实现的策略。有兴趣的可以自己去阅读源码。
到这里,发现关键点又回到了Realm
上面,所以接下来要研究一下两块内容:
-
Authorizer
授权模块是如何实现的? -
Realm
在Shiro
中的默认实现?