shiro认证登录

问题: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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值