分析源码
我们去源码里面去找找,搜索rememberMe:
发现有一个 CookieRememberMeManager 这个类,看名字就知道他多半就是处理 RememberMe 的逻辑,所以根据该类查看它干了什么
这里继承 AbstractRememberMeManager 类,AbstractRememberMeManager 提供了创建和验证这些令牌的方法,以及配置cookie属性的方法。
这是CookieRememberMeManager类的构造函数。首先创建一个名为“rememberMe”的SimpleCookie对象,并将其设置为HttpOnly。然后,将cookie的最大生存期设置为一年,以确保用户在一年内不需要重新登录。最后,将cookie对象赋值给类中的cookie属性。这个构造函数的作用是创建一个CookieRememberMeManager对象,并设置默认的“记住我”cookie配置。如果需要更改cookie的配置,可以使用其他构造函数或通过setter方法来更改属性。
继续往下看,感觉这里是重点有没有.......
看类名可以知道此类用于将序列化后的用户身份信息存储到cookie中。
在方法中,首先通过WebUtils.isHttp(subject)方法判断Subject对象是否为HTTP-aware实例。如果不是,则返回并忽略记住我操作。如果Subject对象是HTTP-aware实例,就可以获取到HttpServletRequest和HttpServletResponse对象,从而可以设置cookie。
接下来是对这些对象执行的操作:
- 通过WebUtils.getHttpRequest(subject)方法获取HttpServletRequest对象。
- 通过WebUtils.getHttpResponse(subject)方法获取HttpServletResponse对象。
- 将序列化后的用户身份信息进行Base64编码。
- 创建一个新的SimpleCookie对象,并将其值设置为Base64编码后的身份信息。
- 将新的cookie对象保存到HttpServletRequest和HttpServletResponse对象中。
所以这个方法的作用是将序列化后的用户身份信息存储到cookie中,并将其保存到HTTP响应中,以便在用户下一次访问网站时自动登录。
方法调用的跟踪
接下来去查看一下该方法在什么地方被调用(快捷键:Ctrl+Alt+Shift+F7)
左下角如果有弹框的话,将所以选项勾上,下拉范围选项框选择所有。
在这可以看到该类继承的 AbstractRememberMeManager 类调用了该方法。
发现这个方法被 rememberIdentity 方法给调用了。
在这里会发现 rememberIdentity 方法会被 onSuccessfulLogin 方法给调用,跟踪到这一步,就看到了 onSuccessfulLogin 登录成功的方法。
当登录成功后会调用 AbstractRememberMeManage.
onSuccessfulLogin 方法,该方法主要实现了生成加密的RememberMe Cookie,然后将RememberMe Cookie设置为用户的Cookie值。在rememberSerializedIdentity 方法里面去实现了。
回到 onSuccessfulLogin 这个地方,这里看到调用了 isRememberMe 很显而易见得发现这个就是一个判断用户是否选择了Remember Me选项。
另一方面 rememberIdentity 方法会调用 rememberSerializedIdentity 方法,到这里为止,我们已经接触到序列化了。
继续跟踪,有一个叫 getRememberedPrincipals 的方法调用getRememberedSerializedIdentity。看名字就知道 getRememberedPrincipals 是一个取得Remember验证的方法。
这里我们再跟进getRememberedPrincipals 方法,(快捷键:Ctrl+Alt+h)
我们继续跟踪 convertBytesToPrincipals方法
因为 convertBytesToPrincipals方法就是处理getRememberedPrincipals 方法 的东西,看名字应该是进行字节转换的。
该方法首先检查是否存在加密服务,如果存在,则使用密钥对字节数组进行解密。接着,该方法使用"deserialize"方法将字节数组序列化。
我们可以先看反序列化
发现有一个反序列化入口 readObject() 这里就是要利用的点
然后看解码那个地方他的逻辑是如何的
该方法名为"decrypt",有一个参数:"encrypted",代表需要解密的字节数组。
该方法首先将需要解密的字节数组赋值给一个名为"serialized"的新的字节数组。然后,该方法通过调用"getCipherService"方法获取加密服务,如果加密服务存在,则用"getDecryptionCipherKey"方法获取解密密钥,并使用加密服务对字节数组进行解密。解密后,将解密后的结果赋值给"serialized"字节数组。最后,该方法返回"serialized"字节数组,即解密后的结果。
在这里我们需要关注俩个点,接口的抽象方法,有两个参数
第一个是要解密的数据
第二个参数就是解密的key了,这个是我们十分关心的,所以我们跟进第二个参数
跟踪返回 decryptionCipherKey
跟踪
setDecryptionCipherKey 方法
跟踪 setCipherKey 方法