接上一篇博客 > Springboot整合Shiro:實現(xiàn)Redis緩存
實現(xiàn)用戶登錄后壤靶,瀏覽器關閉后缓待,再次打開瀏覽器無需重新登錄的功能RememberMe迫皱。
比較簡單,直接上代碼:
(1)ShiroConfig.java
中添加rememberMeManager
的配置
/**
* cookie對象;
* @return
*/
public SimpleCookie rememberMeCookie(){
//這個參數(shù)是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,參數(shù)類型byte[],字節(jié)數(shù)組長度要求16
// cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
cookieRememberMeManager.setCipherKey("ZHANGXIAOHEI_CAT".getBytes())
return cookieRememberMeManager;
}
注意:cookieRememberMeManager.setCipherKey傳入參數(shù)為長度16位的byte[]犀被,否則會報Unable to init cipher instance:無法初始化密碼實例的錯誤,后面會提到原因
(2)注入到SecurityManager
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//將自定義的realm交給SecurityManager管理
securityManager.setRealm(customRealm());
// 自定義緩存實現(xiàn) 使用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","狀態(tài)不正常");
}
if(currentUser.isAuthenticated()){
System.out.println("認證成功");
model.addAttribute("currentUser",currentUser());
return "/success";
}else{
token.clear();
return "login";
}
}
(5)測試結果展示
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
這里說一下cookieRememberMeManager.setCipherKey()方法傳入參數(shù)需要是長度16位的byte[]數(shù)組的原因。
(1)進入cookieRememberMeManager.setCipherKey
方法
public void setCipherKey(byte[] cipherKey) {
this.setEncryptionCipherKey(cipherKey);
this.setDecryptionCipherKey(cipherKey);
}
這里可以得到shiro
用來給rememberMeCookie
加密解密的key的設置贱除,其參數(shù)是byte[]
數(shù)組生闲,并未規(guī)定長度。
(2)設置了加密key后月幌,會調用加密參數(shù)檢查的方法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數(shù)組長度為16, 24,和32扯躺,其他長度就會拋出InvalidKeyException: Invalid AES key length: X bytes
的異常捉兴,那么為什么需要是16位而不能是24和32位呢?我們繼續(xù)往下看录语。
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數(shù)組的長度*8返回澎埠,16位byte數(shù)組返回128虽缕,24返回192,32返回256蒲稳。
順著加密參數(shù)檢查的方法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數(shù)組*8的長度剩胁,16位byte數(shù)組返回128,24返回192祥国,32返回256摧冀。所以只有16位byte數(shù)組才會通過if (var2.maxKeySize <= this.maxKeySize)
的校檢,其他會默認返回false
系宫。
返回false
后,加密參數(shù)檢查的方法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數(shù)組
**
***********************************************************************************
OK扩借!完成~~
共同學習,歡迎指正修改~ 喵喵喵?
下一篇文章:Springboot整合Shiro: MD5加密方式