问题:Shrio是如何通过认证的
背景:使用DefultWebSecurityManage实现用户登录认证,并且自定义一个Realm
线索:
(1)在配置文件中,ShiroFilterFactoryBean依赖与SecurityManager,SecurityManager依赖于Realm,也就是说在代码中这几个之间构成了某种关系
(2)先弄清楚Realm的实现类,Subject的实现类更加容易理解方法的调用,他们贯穿了整个认证流程
基于Shiro实现的登录逻辑
1.用户输入login.do并且加入user和password,回车
2.用户接受参数,并组装为Token,从SecurityUtils中获取Subject
3.调用login方法,开始认证
4.重写的Realm类中,调用doGetAuthenticationInfo(AuthenticationToken token)
5.从token中拿到user,查询数据库,得到User对象
6.并将User对象的信息封装到AuthenticationInfo中,返回Info给SecurityManager
7.manger将拿到的ifo和token进行对比,完成认证
1
2
3
4
5
6
7
分析源码的实现
//开始login
subject.login(token);
1
2
1.DelegatingSubject是这个subject的实现类,里面的Login方法
public class DelegatingSubject implements Subject {
…
public void login(AuthenticationToken token) throws AuthenticationException {
//这个方法是为例清楚session里面的信息
clearRunAsIdentitiesInternal();
/*这里是认证的重点/
/*这里是认证的重点/
/*这里是认证的重点/
Subject subject = securityManager.login(this, token);
/**看上面,主要功能是认证,其他代码省略,可能下一篇会把整个逻辑的代码注解一遍*/
}
…
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2.上面的securityManager.login(this,token),本质上是DefultSecurityManager.login()并且反回一个DelegatingSubject对象
public class DefaultSecurityManager extends SessionsSecurityManager {
…
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
//设置接受AuthenticationInfo
AuthenticationInfo info;
/**处理异常的代码省略*/
...
//最主要的方法
//最主要的方法
//最主要的方法
info = authenticate(token);
//看上面
...
//创建subject对象,里面包含了认证之后几乎所有的信息
Subject loggedIn = createSubject(token, info, subject);
//读取token中的Rememberme信息
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
…
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
3.这个authenticate(token),实际上是AuthenticatingSecurityManager类里面的公有方法,并且DefultSecurityManager的父类的父类的父类是这个类,所以可以直接调用这个方法。
public abstract class AuthenticatingSecurityManager extends RealmSecurityManager {
/*这个Authenticator里面的是ModularRealmAuthenticator,这个类继承了实现Authenticator接口的AbstractAuthenticator类/
private Authenticator authenticator;
public AuthenticatingSecurityManager() {
super();
this.authenticator = new ModularRealmAuthenticator();
}
…
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}
…
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
4.所以这个authenticate(token),实际上是
new ModularRealmAuthenticator().authenticate(token);注意这里面的authenticate()是在AbstractAuthenticator类中被final修饰,所以不能被重写。
public abstract class AbstractAuthenticator implements Authenticator, LogoutAware {
…
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
…
//核心部分
//核心部分
//看函数名是要开始做授权
info = doAuthenticate(token);
//看上面
…
}
…
}
1
2
3
4
5
6
7
8
9
10
11
12
13
6.还是跳到ModularRealmAuthenticator里面的doAuthenticate(token)
public class ModularRealmAuthenticator extends AbstractAuthenticator {
…
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
//获取所有的realm
Collection realms = getRealms();
if (realms.size() == 1) {
//因为我们前面我们只定义了一个Realm,并且通过setRealm(realm),所以调用这个方法
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
//当我们定义多个Realm时到这里
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
…
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
7.选择适合当前Realm的函数经行认证,也就是doSingleRealmAuthenticatication(),这个方法也是ModularRealmAuthenticator类下面的方法
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
//用来判断该Realm是否支持这个token,这里时在定义Realm时决定的
…
}
//核心代码
//核心代码
//核心代码
AuthenticationInfo info = realm.getAuthenticationInfo(token);
//看上面
if (info == null) {
....
//这里面时在验证token时未找到相应的账户信息
}
return info;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
8.在之前也说了,这个realm是我们最开始配置的CoustomRealm类,所以这个调用的getAuthentiactionInfo(token)其实也就是我们自己定义的CoustomRealm所继承自AuthenticatingRealm类的方法
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {
…
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//从认证缓存中拿到token的相关信息,这是第一次认证,故没有缓存数据
AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
/**
核心代码一
主要是通过我们自定义的Realm获得对应的token数据,所以才需要判断是否支持这个token
如:token对应的数据库中的账号、密码等
*/
info = doGetAuthenticationInfo(token);
//看上面
....
if (token != null && info != null) {
//如果成功,缓存身份验证信息
cacheAuthenticationInfoIfPossible(token, info);
}
}
....
if (info != null) {
/**
核心代码二
判断凭证是否匹配,如果不匹配会抛出异常,匹配成功无返回值
*/
assertCredentialsMatch(token, info);
}
.....
return info;
}
…
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
10.前面已经强调了Realm是我们自己定义的CoustomRealm,所以doGetAuthenticationInfo(token)本质上其实是new CoustomRealm().doGetAuthenticationInfo(token)。(下面是我自己自己定义的Realm,可以参考Realm的工作流程)
public class CustomRealm extends AuthorizingRealm {
…
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//权限的认证省略,跟认证查询信息相似
…
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
if (username == null)
throw new UnknownAccountException("用户名不能为空");
UserExample example = new UserExample();
example.or().andUsernameEqualTo(username);
List<User> list = userRepository.selectByExample(example);
if (list.isEmpty())
throw new UnknownAccountException("不存在用户名为[" + username + "]的用户");
if (list.size() != 1)
throw new RuntimeException("数据库错误。存在多位用户名为[" + username + "]的用户");
User user = list.get(0);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, user.getPassword(), CustomRealm.class.getName());
info.setCredentialsSalt(ByteSource.Util.bytes(user.getPasswordSalt()));
return info;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
11.拿到Info信息,里面是包含通过token获取的用户名查到的账号,密码等信息,接下来回到上面的第8步的核心代码二中:assertCredentialsMatch(token, info);
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {
…
//如果匹配失败,就抛出一个IncorrectCredentialsException异常
}
//成功的话结束了这个函数的运行,并且无返回值
} else {
//如果没有CredentialsMatcher 就抛出一个AuthenticationException异常
}
}
1
2
3
4
5
6
7
8
9
10
11
12
12.认证到此结束,当然认证成功之后会通过sessionDao添加信息到相应的session里面。
————————————————
版权声明:本文为CSDN博主「青语玉」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/apache_z/article/details/100118607