認證流程:
1.獲取當前Subject.調用SecurityUtils.getSubject();
2.測試當前用戶是否已經被認證,即是否已經登錄,調用Subject的isAurhenticated();
3.若沒有認證,則把用戶名和密碼封裝成UsernamePasswordToken對象.
對於B/S應用程序來說,一般用戶名和密碼是在前台表單中獲得的:
1.創建一個表單頁面.
2.把請求提交到SpringMVC的Controller.
3.獲取用戶名和密碼.
4.執行登錄:調用Subject.login(AuthenticationToken) 方法.
5.自定義Realm方法,從數據庫中獲取對應的記錄,返回給Shiro.
自定義Realm的實現:
1.繼承org.apache.shiro.realm.AuthenticatingRealm類.
2.實現doGetAuthenticationInfo(AuthenticationToken)方法.
為什么要繼承它且實現它的doGetAuthenticationInfo方法呢?可以跟進源碼查看subject.login(token)
是怎樣工作的:
subject.login(token)
->securityManager.login(this, token)
->authenticate(token)
->authenticator.authenticate(token)
->在org.apache.shiro.authc.AbstractAuthenticator中的doAuthenticate(token)
方法->查看執行單個認證操作doSingleRealmAuthentication(realms.iterator().next(), authenticationToken)
->realm.getAuthenticationInfo(token)
->getCachedAuthenticationInfo(token)
->在getAuthenticationInfo方法中定義了doGetAuthenticationInfo(token)
,所以需要實現doGetAuthenticationInfo方法.
6.由shiro完成對密碼的比對.
數據傳輸流程解析:
在執行subject.login(token)方法后,token對象將會傳到第5步中自定義Realm中的實現的doGetAuthenticationInfo方法中的AuthenticationToken對象中,這樣就可以將token對象傳到Realm域中了,我們可以對token進行比對判斷登錄是否成功.
流程:
前台form表單提交數據->Controller->subject.login(token)->shiro域中接收到token對象.
后台數據驗證流程分析
-
把AuthenticationToken轉換為UsernamePasswordToken.因為我們在之前傳入Token對象的時候就是一個這個對象,所以可以強轉;
通過用戶名把數據從數據庫取出來
因為配置了Jedis緩存,所以先從緩存中取,取不到再去數據庫中取,在數據庫中取出后在放到緩存中
UsernamePasswordToken authenToken = (com.yl.video.security.UsernamePasswordToken) token;
-
從UsernamePasswordToken中取出username;
String userName = token.getUsername();
-
調用數據庫的方法,從數據庫中查詢出對應的用戶記錄,返回一個數據庫中的user對象.
SysUser user = UserUtils.getByLoginName(userName);
-
若登錄失敗,拋出AuthenticationException等異常.此處只拋出一個賬戶鎖定異常.
if (user != null) { if (Global.BOOLFALSE.equals(user.getLocked())){ throw new AuthenticationException("該已帳號禁止登錄."); } }
-
根據用戶情況,構建AuthenticationInfo對象並返回.
AuthenticationInfo
對象是一個接口對象,常用SimpleAuthenticationInfo
對象來實現.
創建一個返回的info對象:
return new SimpleAuthenticationInfo(new Principal(user, authenToken.isMobileLogin()), String.valueOf(user.getPassword()), ByteSource.Util.bytes(userName+user.getSalt()), getName());
三個參數的解釋:
- principal:認證的實體信息,可以是傳過來的user對象,也可以是username等;
- credentials:數據庫中的密碼;
- realmName:當前Realm對象的name,調用父類的getName()方法即可.
注意:需要對shiro進行登出操作,否則會有登錄成功后再登錄錯誤的對象會造成仍然能登錄的情況
密碼比對分析
通過AuthenticatingRealm 的credentialsMatcher 屬性來進行密碼比對 !
- 把密碼字符串加密成MD5;
- 替換當前Realm的credentialsMatcher屬性,直接使用HashedCredentialsMatcher對象,並設置加密算法即可.
如何替換:
在對密碼加密分析的時候,要對兩個密碼進行分析,一個是從前台獲取到的密碼,另一個是在數據庫中獲取到的密碼,分別對兩個密碼進行鹽值加密:
-
對從前台傳過來的token對象: 通過在配置文件中配置加密算法即可自動將密碼加密成想要的結果:
-
從數據庫中傳過來的對象: 因為在shiro的加密中,最后對數據加密是調用了
new SimpleHash(hashAlogorithnName,credentials,salt,hashIterations)
方法,所以對數據庫中的密碼直接進行調用此方法即可. 參數分別為(加密方式,加密的密碼,鹽值,加密次數).
如何使用鹽值加密
- 為什么要對密碼進行加鹽:
在realm進行密碼比對的過程中,當密碼相同的時候,用戶名無須正確只要密碼比對成功即可登錄成功,所以要讓密碼唯一,所以可以對密碼加鹽,其中鹽值必須是唯一的. - 如何對密碼進行鹽值加密:
- 在doGetAuthenticationInfo方法返回值創建SimpleAuthenticationInfo對象的時候,需使用SimpleAuthenticationInfo(principal,credentials,credentialsSalt,realmName)構造器.
- 使用ByteSource.Util.bytes()來計算鹽值.
- 鹽值要唯一:一般使用隨機字符串或id
- 使用new SimpleHash(hashAlogorithnName,credentials,salt,hashIterations)來計算鹽值加密后的值.
多realm認證
多realm認證原理:
多realm認證是通過ModularRealmAuthenticator對象進行認證的,我們可以在spring-shiro中配置多個realm,並將多個bean統一交給ModularRealmAuthenticator進行管理,然后在SecurityManager中配置ModularRealmAuthenticator來進行實現多realm認證的效果。
多realm的認證配置:
在多realm配置的時候一般將兩個或多個realms放在securityManager中而不是放在authenticator中,如下圖:
這樣的好處是當授權的時候比較方便。