Shiro的第一步:登录认证(基于源码的十步分析)

问题:Shrio是如何通过认证的

  1. 背景:使用DefultWebSecurityManage实现用户登录认证,并且自定义一个Realm

  2. 线索:
    (1)在配置文件中,ShiroFilterFactoryBean依赖与SecurityManagerSecurityManager依赖于Realm,也就是说在代码中这几个之间构成了某种关系
    (2)先弄清楚Realm的实现类,Subject的实现类更加容易理解方法的调用,他们贯穿了整个认证流程

  3. 基于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进行对比,完成认证
    

分析源码的实现

//开始login
subject.login(token);

1.DelegatingSubject是这个subject的实现类,里面的Login方法

public class DelegatingSubject implements Subject {
....
	public void login(AuthenticationToken token) throws AuthenticationException {
			//这个方法是为例清楚session里面的信息
	        clearRunAsIdentitiesInternal();
	        /**这里是认证的重点*/
	        /**这里是认证的重点*/
	        /**这里是认证的重点*/
	        Subject subject = securityManager.login(this, token);
	        
			/**看上面,主要功能是认证,其他代码省略,可能下一篇会把整个逻辑的代码注解一遍*/
	       }
....
}

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;
    }
...

}

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);
    }
...
}

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);
		//看上面
	...
	}
...
}

6.还是跳到ModularRealmAuthenticator里面的doAuthenticate(token)

public class ModularRealmAuthenticator extends AbstractAuthenticator {
...
	protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        //获取所有的realm
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
        	//因为我们前面我们只定义了一个Realm,并且通过setRealm(realm),所以调用这个方法
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
        	//当我们定义多个Realm时到这里
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }
...
}

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;
    }

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;
    }
....
}

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;
    }
}

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异常
        }
    }

12.认证到此结束,当然认证成功之后会通过sessionDao添加信息到相应的session里面。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝多芬也爱敲代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值