导语
之前的博客中分享了关于身份认证以及Realm的内容其中提到了一个比较关键的类,AuthenticationInfo也就是认证信息的类。怎么样去获取到这个身份 认证的信息类呢?
Authenticator(认证器)
之前的内容中提到过如果用户通过login方法认证失败之后会返回AuthenticationException类的异常以及其子类的异常。还总结过这些异常分别是什么?这里首先来看一下,Authenticator其实是一个接口,其中提供了一个用来认证身份信息的方法。通过传入的AuthenticationToken来进行身份认证。
public interface Authenticator {
public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)
throws AuthenticationException;
}
如果验证成功将会返回一个AuthenticationInfo的验证信息,这个信息中包含了身份以及凭证。
那么下面就来分析一下接口的实现类有那些。从下面截图中可以看到SecurityManager接口就继承了Authenticator,当然还有一个ModularRealmAuthenticator也实现了,它的作用就是将其委托给多个Realm进行验证,验证规则是通过AuthenticationStrategy接口进行制定的。
而AuthenticationStrategy的默认实现有如下一些
其中
FirstSuccessfulStrategy:表示只要一个Realm验证成功就可以了,只返回第一个Realm身份验证成功的认证信息,将其他的验证忽略。
AtLeastOneSuccessfulStrategy:表示只要有一个Realm验证成功即可,和上面一个不同的是,返回所有的Realm身份验证成功的认证信息。
AllSuccessfulStrategy:所有的Realm都要验证成功才算成功,并且返回所有的Realm身份验证成功的验证信息,如果其中有一个失败就失败了。
在ModularRealmAuthenticator中默认使用的策略是AtLeastOneSuccessfulStrategy。也就是说假如这里有三个Realm
- myRealm1 用户名/密码为test/123时返回成功,并且返回test/123
- myRealm2 用户名/密码为admin/123时返回成功,并且返回admin/123
- myRealm3 用户名/密码为test/123时返回成功,但是返回的身份认证是test@163.com/123
从上面三个返回结果来看,第三个出现了身份认证返回的数据不匹配了。那么会出现什么样的情况呢?接下来就来看看AuthenticationStrategy
AuthenticationStrategy(认证策略)
首先配置如下的策略
# 指定SecurityManager的Authenticator实现
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator
# 指定SecurityManager.authenticator 的实现策略
allSuccessfulStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy
# 声明realm
myRealm1=com.nihui.shiro.realm.realmconfig.MyRealm1
myRealm2=com.nihui.shiro.realm.realmconfig.MyRealm2
myRealm3=com.nihui.shiro.realm.realmconfig.MyRealm3
# 指定SecurityManager的Realm实现
securityManager.realms=$myRealm1,$myRealm2
1、修改之前代码中的内容创建三个Realm如下
Realm1
public class MyRealm1 implements Realm {
public String getName() {
return "myrealm1";
}
public boolean supports(AuthenticationToken token) {
//仅仅支持一个UsernamePaasswordToke
return token instanceof UsernamePasswordToken;
}
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取到用户名和密码
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
if (!"test".equals(username)){
//用户名错误
throw new UnknownAccountException();
}
if (!"123".equals(password)){
//密码错误
throw new IncorrectCredentialsException();
}
//如果身份认证验证成功,返回一个AuthenticationInfo
return new SimpleAuthenticationInfo(username,password,getName());
}
}
Realm2
public class MyRealm2 implements Realm {
public String getName() {
return "myrealm2";
}
public boolean supports(AuthenticationToken token) {
//仅仅支持一个UsernamePaasswordToke
return token instanceof UsernamePasswordToken;
}
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取到用户名和密码
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
if (!"admin".equals(username)){
//用户名错误
throw new UnknownAccountException();
}
if (!"123".equals(password)){
//密码错误
throw new IncorrectCredentialsException();
}
//如果身份认证验证成功,返回一个AuthenticationInfo
return new SimpleAuthenticationInfo(username,password,getName());
}
}
Realm3
public class MyRealm3 implements Realm {
public String getName() {
return "myrealm3";
}
public boolean supports(AuthenticationToken token) {
//仅仅支持一个UsernamePaasswordToke
return token instanceof UsernamePasswordToken;
}
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取到用户名和密码
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
if (!"test".equals(username)){
//用户名错误
throw new UnknownAccountException();
}
if (!"123".equals(password)){
//密码错误
throw new IncorrectCredentialsException();
}
//如果身份认证验证成功,返回一个AuthenticationInfo
username = "test@163.com";
return new SimpleAuthenticationInfo(username,password,getName());
}
}
特别注意的是Realm3 中有如下的改动,当用户认证成功之后,他所返回的认证信息是用户邮箱。
username = "test@163.com";
return new SimpleAuthenticationInfo(username,password,getName());
2、编写测试类
public class AuthenticatorTest {
public static void main(String[] args) {
//测试成功
testAllSuccessFulStrategyWithSuccess();
}
//测试成功
public static void testAllSuccessFulStrategyWithSuccess(){
login("classpath:shiro-authenticator-all-success.ini");
Subject subject = SecurityUtils.getSubject();
PrincipalCollection principalCollection = subject.getPrincipals();
System.out.println(principalCollection.asList().size());
}
private static void login(String configFile){
Factory<SecurityManager> factory = new IniSecurityManagerFactory(configFile);
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("test","123");
subject.login(token);
}
}
会看到上代码最后输出的结果是2,那么为什么会是2呢?其实细心的就会返现其实在PrincipalCollection作为返回值返回的时候,都知道Principal是用户认证成功之后的信息,与用户认证信息是没有关系的。所以说只要用户名和密码操作完成之后,就会在PrincipalCollection放置一条数据,如果当Realm1和Realm3返回的数据相同的时候,PrincipalCollection中只存在一条信息。
自定义认证策略
既然是这样那么就可以实现自定义的认证策略那么如何实现自定义的认证策略呢?首先来分析一下已有的认证策略接口。org.apache.shiro.authc.pam.AuthenticationStrategy
public interface 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;
}
由于每个AuthenticationStrategy实例都是无状态的,所以每次通过接口将相应的认证信息传入下一次流程:通过上面的接口可以合并/返回第一个验证成功的信息。
一般情况自定义的AuthenticationStrategy可以参考它的抽象实现AbstractAuthenticationStrategy也可以参考具体实现来操作。
总结
通过之前三篇博客,介绍了用户身份认证中的三个重要的点,如何进行身份验证、Realm是什么、以及认证原理认证策略。简单的了解了一个整个的认证流程。后面将具体的分析每个功能点。敬请期待!