(轉) 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
}
該文章涉及的代碼
代碼地址
(轉) shiro權限框架詳解04-shiro認證