接上一篇博客 > Springboot整合Shiro:实现Redis缓存
实现用户登录后,浏览器关闭后,再次打开浏览器无需重新登录的功能RememberMe。
比较简单,直接上代码:
(1)ShiroConfig.java
中添加rememberMeManager
的配置
/**
* cookie对象;
* @return
*/
public SimpleCookie rememberMeCookie(){
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//cookie生效时间30天,单位秒;
simpleCookie.setMaxAge(2592000);
return simpleCookie;
}
/**
* cookie管理对象;记住我功能
* @return
*/
public CookieRememberMeManager rememberMeManager(){
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
// cookieRememberMeManager.setCipherKey用来设置加密的Key,参数类型byte[],字节数组长度要求16
// cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
cookieRememberMeManager.setCipherKey("ZHANGXIAOHEI_CAT".getBytes())
return cookieRememberMeManager;
}
注意:cookieRememberMeManager.setCipherKey传入参数为长度16位的byte[],否则会报Unable to init cipher instance:无法初始化密码实例的错误,后面会提到原因
(2)注入到SecurityManager
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//将自定义的realm交给SecurityManager管理
securityManager.setRealm(customRealm());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
// 自定义session管理 使用redis
securityManager.setSessionManager(SessionManager());
// 使用记住我
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
(3)修改登录页面login.html
<form action="/login" method="post">
<span th:text="${msg}" style="color: red"></span><br>
用户名:<input type="text" name="username"><br>
密 码:<input type="password" name="password"><br>
<input type="checkbox" name="rememberMe">记住我<br>
<input type="submit" value="Login">
</form>
(4)修改LoginController.java
@GetMapping({"/","/success"})
public String success(Model model){
Subject currentUser = SecurityUtils.getSubject();
User user = (User) currentUser.getPrincipal();
model.addAttribute("username", user.getUsername());
return "success";
}
@PostMapping("/login")
public String login(String username, String password, boolean rememberMe, Model model){
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
Subject currentUser = SecurityUtils.getSubject();
try {
//主体提交登录请求到SecurityManager
token.setRememberMe(rememberMe);
currentUser.login(token);
}catch (IncorrectCredentialsException ice){
model.addAttribute("msg","密码不正确");
}catch(UnknownAccountException uae){
model.addAttribute("msg","账号不存在");
}catch(AuthenticationException ae){
model.addAttribute("msg","状态不正常");
}
if(currentUser.isAuthenticated()){
System.out.println("认证成功");
model.addAttribute("currentUser",currentUser());
return "/success";
}else{
token.clear();
return "login";
}
}
(5)测试结果展示
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
这里说一下cookieRememberMeManager.setCipherKey()方法传入参数需要是长度16位的byte[]数组的原因。
(1)进入cookieRememberMeManager.setCipherKey
方法
public void setCipherKey(byte[] cipherKey) {
this.setEncryptionCipherKey(cipherKey);
this.setDecryptionCipherKey(cipherKey);
}
这里可以得到shiro
用来给rememberMeCookie
加密解密的key的设置,其参数是byte[]
数组,并未规定长度。
(2)设置了加密key后,会调用加密参数检查的方法passCryptoPermCheck
private boolean passCryptoPermCheck(CipherSpi var1, Key var2, AlgorithmParameterSpec var3) throws InvalidKeyException {
String var4 = this.cryptoPerm.getExemptionMechanism();
int var5 = var1.engineGetKeySize(var2);
int var7 = this.transformation.indexOf(47);
String var6;
if (var7 != -1) {
var6 = this.transformation.substring(0, var7);
} else {
var6 = this.transformation;
}
CryptoPermission var8 = new CryptoPermission(var6, var5, var3, var4);
if (!this.cryptoPerm.implies(var8)) {
if (debug != null) {
debug.println("Crypto Permission check failed");
debug.println("granted: " + this.cryptoPerm);
debug.println("requesting: " + var8);
}
return false;
} else if (this.exmech == null) {
return true;
} else {
try {
if (!this.exmech.isCryptoAllowed(var2)) {
if (debug != null) {
debug.println(this.exmech.getName() + " isn't enforced");
}
return false;
} else {
return true;
}
} catch (ExemptionMechanismException var10) {
if (debug != null) {
debug.println("Cannot determine whether " + this.exmech.getName() + " has been enforced");
var10.printStackTrace();
}
return false;
}
}
}
其中engineGetKeySize
用来获取加密key的size
protected int engineGetKeySize(Key var1) throws InvalidKeyException {
byte[] var2 = var1.getEncoded();
if (!AESCrypt.isKeySizeValid(var2.length)) {
throw new InvalidKeyException("Invalid AES key length: " + var2.length + " bytes");
} else {
return Math.multiplyExact(var2.length, 8);
}
}
static final boolean isKeySizeValid(int var0) {
for(int var1 = 0; var1 < AES_KEYSIZES.length; ++var1) {
if (var0 == AES_KEYSIZES[var1]) {
return true;
}
}
return false;
}
interface AESConstants {
int AES_BLOCK_SIZE = 16;
int[] AES_KEYSIZES = new int[]{16, 24, 32};
}
当用户勾选记住我后请求登录,调用AESCrypt
中的isKeySizeValid
方法进行key长度的校检。
可以看到isKeySizeValid
校检允许的byte数组长度为16, 24,和32,其他长度就会抛出InvalidKeyException: Invalid AES key length: X bytes
的异常,那么为什么需要是16位而不能是24和32位呢?我们继续往下看。
engineGetKeySize
方法通过AESCrypt.isKeySizeValid
验证后会调用Math.multiplyExact(var2.length, 8)
方法,进入 Math.multiplyExact
方法。
public static int multiplyExact(int x, int y) {
long r = (long)x * (long)y;
if ((int)r != r) {
throw new ArithmeticException("integer overflow");
}
return (int)r;
}
在 Math.multiplyExact方法中将byte数组的长度*8返回,16位byte数组返回128,24返回192,32返回256。
顺着加密参数检查的方法passCryptoPermCheck
往下走,有一个this.cryptoPerm.implies
方法
if (!this.cryptoPerm.implies(var8)) {
if (debug != null) {
debug.println("Crypto Permission check failed");
debug.println("granted: " + this.cryptoPerm);
debug.println("requesting: " + var8);
}
return false;
进入this.cryptoPerm.implies
方法
public boolean implies(Permission var1) {
if (!(var1 instanceof CryptoPermission)) {
return false;
} else {
CryptoPermission var2 = (CryptoPermission)var1;
if (!this.alg.equalsIgnoreCase(var2.alg) && !this.alg.equalsIgnoreCase("*")) {
return false;
} else {
if (var2.maxKeySize <= this.maxKeySize) {
if (!this.impliesParameterSpec(var2.checkParam, var2.algParamSpec)) {
return false;
}
if (this.impliesExemptionMechanism(var2.exemptionMechanism)) {
return true;
}
}
return false;
}
}
}
其中:if (var2.maxKeySize <= this.maxKeySize)
判断限制了key的长度。这里的this.maxKeySize
在项目初始化的时候设置了默认值128,var2.maxKeySize
为经过Math.multiplyExact(var2.length, 8)
处理后byte数组*8的长度,16位byte数组返回128,24返回192,32返回256。所以只有16位byte数组才会通过if (var2.maxKeySize <= this.maxKeySize)
的校检,其他会默认返回false
。
返回false
后,加密参数检查的方法passCryptoPermCheck
也会返回false
private void checkCryptoPerm(CipherSpi var1, Key var2, AlgorithmParameterSpec var3) throws InvalidKeyException, InvalidAlgorithmParameterException {
if (this.cryptoPerm != CryptoAllPermission.INSTANCE) {
if (!this.passCryptoPermCheck(var1, var2, (AlgorithmParameterSpec)null)) {
throw new InvalidKeyException("Illegal key size");
} else if (var3 != null && !this.passCryptoPermCheck(var1, var2, var3)) {
throw new InvalidAlgorithmParameterException("Illegal parameters");
}
}
}
然后就会在checkCryptoPerm
方法中抛出InvalidKeyException("Illegal key size")
异常。
***********************************************************************************
**
综上:cookieRememberMeManager.setCipherKey()中传入的key只能为16位长度的byte数组
**
***********************************************************************************
OK!完成~~
共同学习,欢迎指正修改~ 喵喵喵❤
下一篇文章:Springboot整合Shiro: MD5加密方式