关注公众号回复20231110获取最新网络安全以及内网渗透等资料。
文章目录
- 关注公众号回复20231110获取最新网络安全以及内网渗透等资料。
- Shiro的核心架构
- Shiro中的认证
- 认证
- shiro中认证的关键对象
- 认证流程
- 调试认证流程
- Shiro的加密过程
- Shiro中的解密过程
- 总结
Shiro的核心架构
Shiro中的认证
认证
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。
shiro中认证的关键对象
- Subject:主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体; ****
- Principal:身份信息
是主体(subject)进行身份认证的标识,标识必须具有唯一性
,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
- credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
认证流程
调试认证流程
//1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//3.SecurityUtils 给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象 subject主体
Subject subject = SecurityUtils.getSubject();
//5.去创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("123","123456");
try {
System.out.println("认证状态:" + subject.isAuthenticated());
subject.login(token); //用户认证
System.out.println("登录成功");
System.out.println("认证状态:" + subject.isAuthenticated());
}catch (UnknownAccountException e){
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
在subject.login这一行代码打上断点然后我们开始进行调试
可以看到他调用securityManager的login方法 传进去两个参数第一个参数是this 代表当前类对象 第二个token就是我们传进去的AuthenticationToken 就是我们由身份信息和凭证信息组成的令牌 其实我们表面执行的是subject.login方法 其实他底层执行的还是securityManager.login方法 就是我们安全管理器
继续跟进login方法
可以看到这里调用了authenticate方法 将我们token传进去了 这个方法要么在我们的本类 或者在我们的父类 我们跟进去
可以看到他调用了父类的authenticate方法
我们继续跟进去 来到AbstractAuthenticator的authenticate方法
他上线先去判断我们的令牌是否是null 如果是null直接抛出异常 接着去调用dodoAuthenticate方法 把我们的令牌传进去
此时来到了ModularRealmAuthenticator类的doAuthenticate方法
首先执行assertRealmsConfigured方法 我们的realm是否配置 我们是在shiro.ini文件是配置过的
然后调用getRealms方法 拿到我们的所有域 然后进行判断 因为我们的size肯定是等于1
然后我们进去doSingleRealmAuthentication方法
此时来到doSingleRealmAuthentication方法
首先上来先去判断你的realm是否支持token
紧接着调用了realm的getAuthenticationInfo方法 我们跟进去
来到getAuthenticationInfo方法
这里首先从我们的缓存中去拿数据 因为我们是第一次访问 缓存中肯定是没有数据的 所以肯定是null
所以我们进入if判断
进入doGetAuthenticationInfo方法
来到SimpleAccountRealm类的doGetAuthenticationInfo方法
这里没有进行循环调用
首先把我们的token取出 然后强制转换为UsernamePasswordToken
接着调用getUser方法 传进去我们token中的用户名 也就是说根据我们token中的用户名去拿用户
我们跟进进去
首先调用upToken.getUsername()方法 从我们的token中提取到用户名 然后调用getUser方法
这里的this.users 其实就是我们在shiro.ini文件中配置的几个user
这里调用get方法去拿这个用户 如果拿到了就返回SimpleAccount
我们返回
接着我们返回来 接着判断我们的account是不是空的 我们刚在token中给的用户名是不存在的 所以他自然而然也就为空了 最后进行返回
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/e2198c16ddb44187abe0dcd8596235fe.png) 小总结:
其实最后调用了这么多方法 最后执行用户名比较 其实是在SimpleAccountRealm类的doGetAuthenticationInfo方法完成了用户名的校验
我们继续往回走继续来到getAuthenticationInfo方法
这里可以看到判断token如果不为空 并且 info也不为空的话 给他放入缓存
之后进行判断如果info不为空的话 调用assertCredentialsMatch方法 将我们的token和info传进去
我们跟进去assertCredentialsMatch方法
首先拿到了密码匹配器
接着判断我们的密码匹配器是否为空 如果不为空过的话 那么调用密码匹配器的方法去校验
可以看到下面 如果不匹配的话 他会抛异常为IncorrectCredentialsException
我们跟进去doCredentialsMatch方法
可以看到这里调用了getCredentials方法 将我们的token 以及 info信息传递进去 然后进行equals比较 然后返回
这里是因为我们没有涉及到任何加密 所以使用equals进行比较
Shiro的加密过程
看以下图:
当我们去登录之后 去访问account page的时候 他会在cookie中带上我们的rememberMe字段 我们主要看他这里是怎么加密的
经过我们上面说过的他其实是调用了securityManager的login方法 其实最后就是调用了我们的安全管理器里面的login方法
然后他又调用了DefaultSecurityManager的login方法
我们在这个类中搜索一下有没有rememberMe之类方法
在DefaultSecurityManager类中的rememberMeSuccessfulLogin方法 是对我们rememberMe字段的一些操作
我们现在去点击login去登录 然后debug运行 下断点到这里
首先他会通过getRememberMeManager方法 获取到RememberMe
然后进行if判断是否为空 如果不为空的话 那么就进入onSuccessfulLogin方法 将我们的token和info 传递进去
我们跟进去这个方法
可以看到来到了AbstractRememberMeManager类的onSuccessfulLogin方法
这里首先会清除我们的认证信息,然后通过isRememberMe方法 进行判断我们的token中是否有认证信息,
如果有认证信息 那么就进入rememberIdentity方法 我们跟进去
来到rememberIdentity方法
首先他通过getIdentityToRemember方法 获取到我们token中的用户身份
其实就是我们上面写的这行代码 就是我们的令牌 只是上面的代码时通过shiro.ini文件去拿到域中的 也可以通过数据库之类的
UsernamePasswordToken token = new UsernamePasswordToken("123","123456");
然后我们继续进入rememberIdentity方法
来到rememberIdentity方法
这里是通过我们的convertPrincipalsToBytes方法 将我们的用户身份 就是我们token中存储的用户身份 也就是用户名 转为byte字节格式
我们跟进去convertPrincipalsToBytes方法
来到convertPrincipalsToBytes方法
这里首先会将我们的转换为byte字节后的用户身份进行序列化
然后再通过encrypt方法进行加密 我们跟进去
我们来到encrypt方法
这里会通过getCipherService方法获取到密码管理器 然后调用密码管理器的encrypt方法通过密钥进行AES加密
Shiro中有很多加密方式 比如我们常见的md5 salt 散列等等
这里的密钥是通过getEncryptionCipherKey方法获取到的
将我们转换为byte字节后的用户身份(也就是我们的root) 以及序列化之后 然后通过密钥进行AES加密
我们跟进去getEncryptionCipherKey方法
可以看到他是返回一个encryptionCipherKey 我们看到他是一个属性 类型是byte数组 我们去看哪里对encryptionCipherKey属性进行了赋值
我们可以看到 这里通过setEncryptionCipherKey方法对encryptionCipherKey属性进行了赋值 我们去看哪里调用了setEncryptionCipherKey方法
我们来到这里 setCipherKey调用了setEncryptionCipherKey方法 进行了赋值 我们继续找哪里调用了setCipherKey
因为现在这个值我们不知道是多少 所以需要继续往前找
我们可以清楚的看到 这里通过我们的构造器对encryptionCipherKey进行了赋值
DEFAULT_CIPHER_KEY_BYTES 就是我们的key 我们点进去查看
可以看到 他的key值是写死的
convertPrincipalsToBytes方法加密完之后我们进行返回
来到rememberSerializedIdentity方法 我们跟进去
最后通过Base64编码之后 设置到cookie中
Shiro中的解密过程
我们来到DefaultSecurityManager类的getRememberedIdentity方法
首先获取到rememberMe 然后进行判断 是否等于空 如果不等于空的话 调用getRememberedPrincipals解密方法
我们跟进去
来到getRememberedPrincipals方法
这里会调用getRememberedSerializedIdentity和convertBytesToPrincipals方法
getRememberedSerializedIdentity方法 会读取我们的cookie然后进行base64解码 因为我们上面加密的时候 会进行base64编码 到解密这里会进行base64解码
convertBytesToPrincipals方法会对cookie进行解密 并且反序列化
我们跟进去getRememberedSerializedIdentity方法
这里会调用getCookie获取到cookie之后 然后调用Base64的decode方法进行加密 然后返回
我们进入到convertBytesToPrincipals方法
这里对我们的解密的base64 byte字节 进行AES解密 并且对他进行反序列化
总结
到这里Shiro的加密和解密就说完了 其实发现 不管我们认证的过程 还是 AES加密和解密的过程 全都在DefaultSecurityManager类中的一些方法中实现的 紧接着会调用其他类。