你真的了解Shiro框架吗?

2023-12-13 21:03:31

关注公众号回复20231110获取最新网络安全以及内网渗透等资料。

在这里插入图片描述

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类中的一些方法中实现的 紧接着会调用其他类。

文章来源:https://blog.csdn.net/weixin_65550121/article/details/134980392
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。