SpringShiro认证源码详解

橙三吉。 提交于 2020-01-25 05:20:50

Authentication 认证

认证(Authentication):身份验证的过程,也就是证明一个用户的真实身份。为了证明用户身份,需要提供系统理解和相信的身份信息和证据。
需要通过向 Shiro 提供用户的身份(principals)和证明(credentials )来判定是否和系统所要求的匹配。

  • Principals(身份)
    是Subject的“标识属性”,可以是任何与Subject相关的标识,比如说名称(给定名称)、名字(姓或者昵称)、用户名、安全号码等等,当然像昵称这样的内容不能很好的对Subject进行独特标识,所以最好的身份信息(Principals)是使用在程序中唯一的标识–典型的使用用户名或邮件地址。
    Primary Principals(主要身份)
    虽然Shiro允许用户可以使用多个身份,但是还是希望用户能有一个精准表明用户的身份,一个仅有的唯一标识 Subject 值。在多数程序中经常会是一个用户名、邮件地址或者全局唯一的用户 ID。

  • Credentials(证明)
    通常是只有Subject自己才知道的机密内容,用来证明Subject真正拥有所需的身份。一些简单的证书例子如密码、指纹、眼底扫描和X.509证书等。

最常见的身份/证明是用户名和密码,用户名是所需的身份说明,密码是证明身份的证据。如果一个提交的密码和系统要求的一致,程序就认为该用户身份正确,因为其他人不应该知道同样的密码。

Authenticating Subjects

Subjects的验证过程可以分为以下几个步骤:

  1. 收集 Subject 提交的身份和证明;
  2. 向 Authentication 提交身份和证明;
  3. 如果提交的内容正确,允许访问,否则重新尝试验证或阻止访问

如何操作?

第一步:收集Subject提交的身份和证明

//最常用的情况是 username/password:
UsernamePasswordToken token = new UsernamePasswordToken(username, password);

//”Remember Me” 功能是内建的
token.setRememberMe(true);

我们使用 UsernamePasswordToken,支持所有常用的用户名/密码验证途径,这是一个 org.apache.shiro.authc.AuthenticationToken 接口的实现,这个接口被 Shiro 认证系统用来提交身份和证明。
注意 Shiro 并不关心你如何获取这些信息:也许是用户从一个HTML表单中提交的,或者可能从一个 HTTP 请求字串中解析的,也可能来自于Swing或者 Flex GUI 的密码表单,或者通过命令行参数得到。从程序终端用户获取信息的过程与 Shiro 的 AuthenticationToken 完全无关。
你可以随自己喜欢构造和引用 AuthenticationToken 实例 – 这是协议无关的。

第二步:向Authentication提交身份和证明

Subject currentUser = SecurityUtils.getSubject();

currentUser.login(token);

在获取当前执行的 Subject 后,我们执行一个单独的 login) 命令,将之前创建的 AuthenticationToken 实例传给它。
调用 login 方法将有效地执行身份验证。
如果你是第一次接触shiro框架,你肯定不明白login是怎么验证的,下面就来看一看源码:
这时我们已经实例化了一个UsernamePasswordToken对象了。
我们看看login这个方法:
在这里插入图片描述
主要还是用到了securityManager安全管理器
进入securityManager里边的login方法,看看他的实现:
在这里插入图片描述
在这个方法中定义了AuthenticationInfo对象来接受从Realm传来的认证信息
进入authenticate方法中

public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
}

发现调用了authenticator的authenticate这个方法
进入this.authenticator.authenticate(token)这个方法中
在这里插入图片描述

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        this.assertRealmsConfigured();
        Collection<Realm> realms = this.getRealms();
        return realms.size() == 1?this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken):this.doMultiRealmAuthentication(realms, authenticationToken);
}

在这里才是刚才前边的那个authenticator的实现, this.assertRealmsConfigured() 这个方法是判断realm是否存在,不存在则抛出异常,他会根据realm的个数来判断执行哪个方法,上篇中springboot整合shiro我只配置了一个realm,所以他只会执行this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken)这个方法,并且会将 realm和token作为参数传入,这里的realm其实就是自己定义的MyShiroRealm

接下来再进入doSingleRealmAuthentication这个方法中
在这里插入图片描述
在这里 他会先判断realm是否支持token
接下来执行else中的getAuthenticationInfo方法
在这里插入图片描述
this.getCachedAuthenticationInfo(token)这个方法是从shiro缓存中读取用户信息,如果没有,才从realm中获取信息。如果是第一次登陆,缓存中肯定没有认证信息,所以会执行this.doGetAuthenticationInfo(token)这个方法。
查看this.doGetAuthenticationInfo(token)这个方法就会发现有我们自己定义的类供我们选择。
所以 ,上边的doGetAuthorizationInfo是 执行的我们自定义realm中重写的doGetAuthorizationInfo这个方法。这个方法就会从数据库中读取我们所需要的信息,最后封装成SimpleAuthorizationInfo返回去。
现在获取到认证信息了,接下来就是shiro怎么去进行认证,我们返回去看
在这里插入图片描述
获取 完信息之后就是进行密码匹配,进入assertCredentialsMatch方法中看一下
在这里插入图片描述
关于创建的CredentialsMatcher对象还有需要补充的。
SimpleCredentialsMatcher不进行加密,仅仅匹配密码对应的字节数组。
所以代码中用户lg的登陆密码为123,则数据库中的密码也是明文123。
我们在存入数据库的时候会对密码进行加密。
再看一下cm.doCredentialsMatch(token,info)
在这里插入图片描述
这里会用到equals方法对token中加密的密码和从数据库中取出来的info中的密码进行对比,如果认证相同就返回true,失败就返回false,并抛出AuthenticationException,将info返回到defaultSecurityManager中,到此认证过程结束。

第三步:处理提交成功和提交失败

当login函数没有返回信息时表明验证通过了。程序可以继续运行,此时执行 SecurityUtils.getSubject() 将返回验证后的 Subject 实例,subject.isAuthenticated() 将返回true。
但是如果 login 失败了呢?例如,用户提供了一个错误的密码或者因访问系统次数过多而被锁定将会怎样呢?
Shiro拥有丰富的运行期异常AuthenticationException可以精确标明为何验证失败,你可以将 login 放入到 try/catch 块中并捕获所有你想捕获的异常并对它们做出处理。
例如:

try {
    currentUser.login(token);
} catch ( UnknownAccountException uae ) { ...
} catch ( IncorrectCredentialsException ice ) { ...
} catch ( LockedAccountException lae ) { ...
} catch ( ExcessiveAttemptsException eae ) { ...
} ... 捕获你自己的异常 ...
} catch ( AuthenticationException ae ) {
    //未预计的错误?
}

//没问题,继续

如果原有的异常不能满足你的需求,可以创建自定义的AuthenticationExceptions 来表示特定的失败场景。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!