(轉) shiro權限框架詳解04-shiro認證


http://blog.csdn.net/facekbook/article/details/54906635

shiro認證

本文介紹shiro的認證功能

  • 認證流程
  • 入門程序(用戶登錄和退出)
  • 自定義Realm
  • 散列算法

認證流程

開始構造SecurityManager環境subject.login();提交認證securityManager.login()執行認證Authenticator執行認證Realm根據身份獲取驗證信息結束

入門程序(用戶登錄和退出)

創建Java項目

jdk版本:1.7.0_67

加入shiro的jar包以及依賴包

log4j.properties日志文件配置

log4j.rootLogger=debug, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n 

 

eclipse中ini文件打開方式配置

shiro使用ini文件作為配置文件。所以需要修改eclipse中ini文件的打開方式,默認的話ini文件是使用記事本打開。具體如下圖: 

創建ini配置文件

在classpath路徑下創建shiro-first.ini文件,文件內容是測試用戶的賬號和密碼。內容如下:

[users] zhangsan=123 lisi=456

 

認證代碼

@Test public void testLoginAndLogOut() { // 構建SecurityManager工廠,IniSecurityManagerFactory可以從ini文件中初始化SecurityManager環境 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-first.ini"); // 通過工廠創建SecurityManager SecurityManager securityManager = factory.getInstance(); // 將SecurityManager設置到運行環境中 SecurityUtils.setSecurityManager(securityManager); //創建一個Subject實例,該實例認證需要使用上面創建的SecurityManager Subject subject = SecurityUtils.getSubject(); //創建token令牌,賬號和密碼是ini文件中配置的 AuthenticationToken token = new UsernamePasswordToken("zhangsan", "123"); try { //用戶登錄 subject.login(token); } catch (AuthenticationException e) { e.printStackTrace(); } //用戶認證狀態 Boolean isAuthenticated = subject.isAuthenticated(); System.out.println("用戶認證狀態:"+isAuthenticated);//輸出true //用戶退出 subject.logout(); isAuthenticated = subject.isAuthenticated(); System.out.println("用戶認證狀態:"+isAuthenticated);//輸出false }

 

認證執行流程

1.創建token令牌,token中有用戶提交的認證信息即賬號和密碼。 
2.執行subject.login(token),最終由securityManager通過 Authenticator進行認證。 
3.Authenticator的實現ModuleRealmAuthenticator調用realm從init文件讀取用戶真實的賬號和密碼,這里使用的是IniRealm(Shiro自帶) 
4.IniRealm先根據token中的賬號去ini中找該賬號,如果找不到則給ModuleRealmAuthenticator返回null,如果找到則匹配密碼,匹配密碼成功則認證通過。

常見的異常

  • UnknownAccountException 
    賬號不存在異常如下:
org.apache.shiro.authc.UnknownAccountException: Realm [org.apache.shiro.realm.text.IniRealm@9cdc393] was unable to find account data for the submitted AuthenticationToken [org.apache.shiro.authc.UsernamePasswordToken - zhangsan1
  • IncorrectCredentialsException 
    當輸入密碼錯誤會拋出此異常,如下:
org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - zhangsan, rememberMe=false] did not match the expected credentials.

 

更多異常信息如下: 
DisabledAccountException(帳號被禁用) 
LockedAccountException(帳號被鎖定) 
ExcessiveAttemptsException(登錄失敗次數過多) 
ExpiredCredentialsException(憑證過期)等 
類結構如下圖 

自定義Realm

上面的程序使用的是Shiro自帶的IniRealm,IniRealm從ini配置文件中讀取用戶的信息。但是實際情況中大部分情況下是從數據庫中獲取用戶信息,所以需要自定義realm。

Shiro中Realm

 
最基礎的是Realm接口,CachingRealm負責緩存管理,AuthenticatingRealm負責認證,AuthorizingRealm負責授權,通常自定義的Realm繼承AuthorizingRealm。

自定義Realm代碼

通過繼承AuthorizingRealm類實現

public class CustomRealm extends AuthorizingRealm { /** * 認證方法 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 從token中獲取用戶身份信息 String username = (String) token.getPrincipal(); // 正常邏輯應該是通過username查詢數據庫。 // 如果查詢不到返回null if (!"zhangsan".equals(username)) {// 這里模仿查詢不到 return null; } // 模擬從數據獲取密碼 String password = "123"; // 返回認證信息交由父類AuthorizingRealm認證 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, ""); return authenticationInfo; } /** * 授權方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } }

 

ini配置文件

新建shiro-realm.ini文件。內容如下:

[main]
#自定義realm customRealm=com.knight.shiro.realm.CustomRealm #將realm設置到securityManager securityManager.realm=$customRealm

 

這里不需要配置users,是因為我們這里模擬users的獲取來自數據庫。

測試代碼

@Test public void testCustomeRealm() { // 構建SecurityManager工廠,IniSecurityManagerFactory可以從ini文件中初始化SecurityManager環境 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini"); // 通過工廠創建SecurityManager SecurityManager securityManager = factory.getInstance(); // 將SecurityManager設置到運行環境中 SecurityUtils.setSecurityManager(securityManager); //創建一個Subject實例,該實例認證需要使用上面創建的SecurityManager Subject subject = SecurityUtils.getSubject(); //創建token令牌,賬號和密碼是ini文件中配置的 //AuthenticationToken token = new UsernamePasswordToken("zhangsan", "123");//賬號密碼正確token //AuthenticationToken token = new UsernamePasswordToken("zhangsan", "1234");//密碼錯誤異常token AuthenticationToken token = new UsernamePasswordToken("zhangsan1", "123");//賬號錯誤異常token try { //用戶登錄 subject.login(token); } catch (AuthenticationException e) { e.printStackTrace(); } //用戶認證狀態 Boolean isAuthenticated = subject.isAuthenticated(); System.out.println("用戶認證狀態:"+isAuthenticated);//輸出true }

 

散列算法

散列算法一般用於生成一段文本的摘要信息,散列算法不可逆,也就是將內容生成摘要,但是反過來通過摘要生成內容是不可以的。散列算法常用於對密碼進行散列,常用的散列算法有MD5、SHA。一般散列算法需要提供一個salt(鹽)與原始內容生成摘要,這樣做的目的是為了安全性。

例子

        Md5Hash  md5Hash = new Md5Hash("111111"); System.out.println("md5加密,不加鹽:"+md5Hash.toString()); //md5加密,加鹽,一次hash String password_md5_sale_1 = new Md5Hash("11111", "aga23", 1).toString(); System.out.println("md5加密,加鹽,一次hash:"+password_md5_sale_1); //md5加密,加鹽,兩次hash String password_md5_sale_2 = new Md5Hash("11111", "aga23", 2).toString(); System.out.println("md5加密,加鹽,兩次hash:"+password_md5_sale_2);//相當於md5(md5('1111')) //使用simpleHash String simpleHash = new SimpleHash("MD5", "11111", "aga23", 1).toString(); System.out.println(simpleHash);

 

在realm中使用散列算法

實際系統中是將鹽和散列后的值存儲在數據庫中,自定義realm從數據庫取出鹽和加密后的值由shiro完成密碼校驗。

自定義支持散列的realm

public class CustomRealmMd5 extends AuthorizingRealm { /** * 認證方法 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 從token中獲取用戶身份信息 String username = (String) token.getPrincipal(); // 正常邏輯應該是通過username查詢數據庫。 // 如果查詢不到返回null if (!"zhangsan".equals(username)) {// 這里模仿查詢不到 return null; } // 模擬從數據獲取密碼 String password = "fdf907b0d3f427b9ffa2f86f213d1afd"; // 鹽 String salt = "aga23"; // 返回認證信息交由父類AuthorizingRealm認證 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(salt), ""); return authenticationInfo; } /** * 授權方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } }

 

支持散列的realm配置

[main]
#定義憑證匹配器 credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher #設置散列算法 credentialsMatcher.hashAlgorithmName=md5 #設置散列次數 credentialsMatcher.hashIterations=1 #將憑證匹配器設置到realm customRealm=com.knight.shiro.realm.CustomRealmMd5 customRealm.credentialsMatcher=$credentialsMatcher securityManager.realms=$customRealm 

 

測試代碼

注意修改配置文件的路徑

    @Test public void testCustomeRealmMd5() { // 構建SecurityManager工廠,IniSecurityManagerFactory可以從ini文件中初始化SecurityManager環境 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm-md5.ini"); // 通過工廠創建SecurityManager SecurityManager securityManager = factory.getInstance(); // 將SecurityManager設置到運行環境中 SecurityUtils.setSecurityManager(securityManager); //創建一個Subject實例,該實例認證需要使用上面創建的SecurityManager Subject subject = SecurityUtils.getSubject(); //創建token令牌,賬號和密碼是ini文件中配置的 //AuthenticationToken token = new UsernamePasswordToken("zhangsan", "11111");//賬號密碼正確token AuthenticationToken token = new UsernamePasswordToken("zhangsan", "1234");//密碼錯誤異常token //AuthenticationToken token = new UsernamePasswordToken("zhangsan1", "11111");//賬號錯誤異常token try { //用戶登錄 subject.login(token); } catch (AuthenticationException e) { e.printStackTrace(); } //用戶認證狀態 Boolean isAuthenticated = subject.isAuthenticated(); System.out.println("用戶認證狀態:"+isAuthenticated);//輸出true }

 

該文章涉及的代碼

代碼地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM