用户认证器authenticator源码实现解析
- 1、shiro整体架构
- 2、shiro认证用户登录流程
- 2.1、【UserRealmDemo】创建subject主体,并调用login方法
- 2.2、【DelegatingSubject】实现subject的login方法
- 2.3、【DefaultSecurityManager】实现SecurityManager的login方法
- 2.4、【AuthenticatingSecurityManager】实现SecurityManager的authenticate方法
- 2.5、【AbstractAuthenticator】实现authenticator的authenticate方法
- 2.6、【ModularRealmAuthenticator】实现authenticator的doAuthenticate方法
- 2.7、【AuthenticatingRealm】实现realm域的getAuthenticationInfo()方法
- 2.8、【UserRealm】该类实现自定义doGetAuthenticationInfo()方法
- 3、总结
1、shiro整体架构
shiro中,认证器authenticator、授权器authorizer和用于提供用户认证、授权信息的realm域,统一交由SecurityManager来进行调度。
2、shiro认证用户登录流程
2.1、【UserRealmDemo】创建subject主体,并调用login方法
public static void main(String[] args) {
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "1111");
try {
subject.login(token);
System.out.println("登陆成功");
} catch (UnknownAccountException e) {
System.out.println("用户名错误");
e.printStackTrace();
} catch (ExpiredCredentialsException e){
System.out.println("凭证过期");
e.printStackTrace();
} catch (IncorrectCredentialsException e){
System.out.println("密码错误");
e.printStackTrace();
} catch (Exception e){
System.out.println("登陆失败");
e.printStackTrace();
}
System.out.println(subject.isPermittedAll("user:add", "user:delete"));
}
- SecurityManagerFactory工厂类加载shiro.ini配置文件,获取securitymanager;
- 将其设置到SecurityUtils工具类中,并生成得到主体subject;
- 新建UsernamePasswordToken对象,传入用户名密码,模拟前台数据输入;
- 调用主体subject的login方法,传入前台token作为参数,并调用try-catch块捕获异常,来确定是否验证成通过。
2.2、【DelegatingSubject】实现subject的login方法
public void login(AuthenticationToken token) throws AuthenticationException {
clearRunAsIdentitiesInternal();
Subject subject = securityManager.login(this, token);
PrincipalCollection principals;
String host = null;
。。。。。。
}
该login方法将控制权交给SecurityManager的login方法来实现,传入自身引用和token对象。
若成功,则返回带有info信息的subject对象。
2.3、【DefaultSecurityManager】实现SecurityManager的login方法
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);
} catch (AuthenticationException ae) {
。。。
}
Subject loggedIn = createSubject(token, info, subject);
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
方法中调用由AuthenticatingSecurityManager实现的authenticate方法,传入token参数。
2.4、【AuthenticatingSecurityManager】实现SecurityManager的authenticate方法
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}
public AuthenticatingSecurityManager() {
super();
this.authenticator = new ModularRealmAuthenticator();
}
该类的authenticate方法起到一个中转的作用,调用本身authenticator对象的authenticate方法,而此对象通过new ModularRealmAuthenticator() 新建。
2.5、【AbstractAuthenticator】实现authenticator的authenticate方法
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
if (token == null) {
throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
}
log.trace("Authentication attempt received for token [{}]", token);
AuthenticationInfo info;
try {
info = doAuthenticate(token);
if (info == null) {
String msg = "No account information found for authentication token [" + token + "] by this " +
"Authenticator instance. Please check that it is configured correctly.";
throw new AuthenticationException(msg);
}
} catch (Throwable t) {
。。。
}
log.debug("Authentication successful for token [{}]. Returned account [{}]", token, info);
notifySuccess(token, info);
return info;
}
该类的authenticate方法调用由其子类ModularRealmAuthenticator实现的doAuthenticate方法。
2.6、【ModularRealmAuthenticator】实现authenticator的doAuthenticate方法
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
- 该类的duAuthenticate()方法首先执行assertRealmsConfigured()方法,来读取并加载shiro.ini中配置的userRealm自定义域:
protected void assertRealmsConfigured() throws IllegalStateException {
Collection<Realm> realms = getRealms();
if (CollectionUtils.isEmpty(realms)) {
String msg = "Configuration error: No realms have been configured! One or more realms must be " +
"present to execute an authentication attempt.";
throw new IllegalStateException(msg);
}
}
- 加载realms域后,判断如果是单个realm执行doSingleRealmAuthentication()方法, 如果是多个realm则执行doMultiRealmAuthentication()方法
本次是单个realm,执行doSingleRealmAuthentication() 方法, 传入加载的realm域,并在其中调用自定义realm域的getAuthenticationInfo()方法:
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;
}
2.7、【AuthenticatingRealm】实现realm域的getAuthenticationInfo()方法
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
info = doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
该方法调用自定义realm域中写的doGetAuthenticationInfo(token)方法,通过token中的用户名获取数据库中对应的密码信息,并将其封装为AuthenticationInfo对象返回,再执行assertCredentialsMatch(token, info)方法判断密码是否一致,若不一致抛出异常,一致则证明认证通过。
2.8、【UserRealm】该类实现自定义doGetAuthenticationInfo()方法
public class UserRealm extends AuthorizingRealm {
@Override
public String getName() {
return "userRealm";
}
//完成身份认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
Object principal = token.getPrincipal();
//此处模拟从数据库根据用户名查询密码和盐值
String pwd = "47253c7d26d81ca795eabb5314aeffac";
String salt = "salttest";
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, pwd, ByteSource.Util.bytes(salt), getName());
return info;
}
//完成授权信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
。。。
}
}
UserRealm类即为自定义认证和授权的类,用户可以通过extends关键字继承AuthorizingRealm类,重写doGetAuthenticationInfo()方法来实现自己的业务目标。
3、总结
这一套流程捋清楚之后,其实很简单,只是为了实现根据用户名(Principal)获取用户密码(Credential),然后和用户输入的密码进行比较来验证是否认证通过,中间任何一个环节出现问题都会抛出异常,即认证失败。
可是梳理起来为什么涉及到这么多接口、方法、实现类呢?个人理解应该是为了实现低耦合,同时方便对功能进行扩展。