一,shiro架構
1)subject
Subject即主體,外部應用與subject進行交互,subject記錄了當前操作用戶,將用戶的概念理解爲當前操作的主體,可能是一個通過瀏覽器請求的用戶,也可能是一個運行的程序。 Subject在shiro中是一個接口,接口中定義了很多認證授相關的方法,外部程序通過subject進行認證授,而subject是通過SecurityManager安全管理器進行認證授權
2)securityManager
SecurityManager即安全管理器,對全部的subject進行安全管理,它是shiro的核心,負責對所有的subject進行安全管理。通過SecurityManager可以完成subject的認證、授權等,實質上SecurityManager是通過Authenticator進行認證,通過Authorizer進行授權,通過SessionManager進行會話管理等。
SecurityManager是一個接口,繼承了Authenticator, Authorizer, SessionManager這三個接口。
3)Authenticator:Authenticator即認證器,對用戶身份進行認證,Authenticator是一個接口,shiro提供ModularRealmAuthenticator實現類,通過ModularRealmAuthenticator基本上可以滿足大多數需求,也可以自定義認證器。
4)Authorizer:Authorizer即授權器,用戶通過認證器認證通過,在訪問功能時需要通過授權器判斷用戶是否有此功能的操作權限。
5)realm:Realm即領域,相當於datasource數據源,securityManager進行安全認證需要通過Realm獲取用戶權限數據,比如:如果用戶身份數據在數據庫那麼realm就需要從數據庫獲取用戶身份信息。
注意:不要把realm理解成只是從數據源取數據,在realm中還有認證授權校驗的相關的代碼。
6)sessionManager:sessionManager即會話管理,shiro框架定義了一套會話管理,它不依賴web容器的session,所以shiro可以使用在非web應用上,也可以將分佈式應用的會話集中在一點管理,此特性可使它實現單點登錄。
7)SessionDAO:SessionDAO即會話dao,是對session會話操作的一套接口,比如要將session存儲到數據庫,可以通過jdbc將會話存儲到數據庫。
8)CacheManager:CacheManager即緩存管理,將用戶權限數據存儲在緩存,這樣可以提高性能。
9)Cryptography:Cryptography即密碼管理,shiro提供了一套加密/解密的組件,方便開發。比如提供常用的散列、加/解密等功能。
二,shiro的jar包
與其它java開源框架類似,將shiro的jar包加入項目就可以使用shiro提供的功能了。shiro-core是核心包必須選用,還提供了與web整合的shiro-web、與spring整合的shiro-spring、與任務調度quartz整合的shiro-quartz等,下邊是shiro各jar包的maven座標。
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>1.2.3</version>
</dependency>
也可以通過引入shiro-all包括shiro所有的包:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
三,shiro認證
四,代碼示例
1) 1、首先準備一些用戶身份/憑據(shiro.ini)
[users]
zhangsan=123
lisi=22222
2.登錄退出
// 用戶登陸、用戶退出
@Test
public void testLoginLogout() {
// 構建SecurityManager工廠,IniSecurityManagerFactory可以從ini文件中初始化SecurityManager環境
Factory<SecurityManager> factory = new IniSecurityManagerFactory(
"classpath:shiro.ini");
// 通過工廠創建SecurityManager
SecurityManager securityManager = factory.getInstance();
// 將securityManager設置到運行環境中
SecurityUtils.setSecurityManager(securityManager);
// 創建一個Subject實例,該實例認證要使用上邊創建的securityManager進行
Subject subject = SecurityUtils.getSubject();
// 創建token令牌,記錄用戶認證的身份和憑證即賬號和密碼
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
try {
// 用戶登陸
subject.login(token);
} catch (AuthenticationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 用戶認證狀態
Boolean isAuthenticated = subject.isAuthenticated();
System.out.println("用戶認證狀態:" + isAuthenticated);
// 用戶退出
subject.logout();
isAuthenticated = subject.isAuthenticated();
System.out.println("用戶認證狀態:" + isAuthenticated);
}
2)執行流程
1、 創建token令牌,token中有用戶提交的認證信息即賬號和密碼
2、 執行subject.login(token),最終由securityManager通過Authenticator進行認證
3、 Authenticator的實現ModularRealmAuthenticator調用realm從ini配置文件取用戶真實的賬號和密碼,這裏使用的是IniRealm(shiro自帶)
4、 IniRealm先根據token中的賬號去ini中找該賬號,如果找不到則給ModularRealmAuthenticator返回null,如果找到則匹配密碼,匹配密碼成功則認證通過。
3)常見的異常
UnknownAccountException 賬號不存在異常如下:
org.apache.shiro.authc.UnknownAccountException: No account found for user。。。。
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
package com.example.shiro.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
public class CustomRealm extends AuthorizingRealm {
// 設置realm的名稱
@Override
public void setName(String name) {
super.setName("customRealm");
}
// 用於認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
// token是用戶輸入的
// 第一步從token中取出身份信息
String userCode = (String) token.getPrincipal();
//String username = (String)token.getPrincipal(); //得到用戶名
// String password = new String((char[])token.getCredentials()); //得到密碼
// 第二步:根據用戶輸入的userCode從數據庫查詢
// ....
// 如果查詢不到返回null
//數據庫中用戶賬號是zhangsansan
/*if(!userCode.equals("zhangsansan")){//
return null;
}*/
// 模擬從數據庫查詢到密碼
String password = "111112";
// 如果查詢到返回認證信息AuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
userCode, password, this.getName());
return simpleAuthenticationInfo;
}
// 用於授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
// TODO Auto-generated method stub
return null;
}
}
ini配置文件指定自定義Realm實現(shiro-realm.ini)
#聲明一個realm
customRealm=com.example.shiro.realm.CustomRealm
#指定securityManager的realms實現
securityManager.realms=$customRealm
3測試// 自定義realm
@Test
public void testCustomRealm() {
// 創建securityManager工廠,通過ini配置文件創建securityManager工廠
Factory<SecurityManager> factory = new IniSecurityManagerFactory(
"classpath:shiro-realm.ini");
// 創建SecurityManager
SecurityManager securityManager = factory.getInstance();
// 將securityManager設置當前的運行環境中
SecurityUtils.setSecurityManager(securityManager);
// 從SecurityUtils裏邊創建一個subject
Subject subject = SecurityUtils.getSubject();
// 在認證提交前準備token(令牌)
// 這裏的賬號和密碼 將來是由用戶輸入進去
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
"111111");
try {
// 執行認證提交
subject.login(token);
} catch (AuthenticationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 是否認證通過
boolean isAuthenticated = subject.isAuthenticated();
System.out.println("是否認證通過:" + isAuthenticated);
subject.logout();
System.out.println("是否認證通過:" + subject.isAuthenticated());
}
2)配置多個realm
#聲明一個realm
myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1
myRealm2=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2
#指定securityManager的realms實現
securityManager.realms=$myRealm1,$myRealm2
六,散列算法
自定義realm
package com.example.shiro.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
public class CustomRealmMd5 extends AuthorizingRealm {
// 設置realm的名稱
@Override
public void setName(String name) {
super.setName("customRealmMd5");
}
// 用於認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
// token是用戶輸入的
// 第一步從token中取出身份信息
String userCode = (String) token.getPrincipal();
// 第二步:根據用戶輸入的userCode從數據庫查詢
// ....
// 如果查詢不到返回null
// 數據庫中用戶賬號是zhangsansan
/*
* if(!userCode.equals("zhangsansan")){// return null; }
*/
// 模擬從數據庫查詢到密碼,散列值
String password = "36f2dfa24d0a9fa97276abbe13e596fc";
// 從數據庫獲取salt
String salt = "qwerty";
//上邊散列值和鹽對應的明文:111111
// 如果查詢到返回認證信息AuthenticationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
userCode, password, ByteSource.Util.bytes(salt), this.getName());
return simpleAuthenticationInfo;
}
// 用於授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
// TODO Auto-generated method stub
return null;
}
}
2)配置文件(shiro-realm-md5.ini)
[main]
#定義憑證匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次數
credentialsMatcher.hashIterations=2
#將憑證匹配器設置到realm
customRealm=com.example.shiro.realm.CustomRealmMd5
customRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$customRealm
3)測試
// 自定義realm實現散列值匹配
@Test
public void testCustomRealmMd5() {
// 創建securityManager工廠,通過ini配置文件創建securityManager工廠
Factory<SecurityManager> factory = new IniSecurityManagerFactory(
"classpath:shiro-realm-md5.ini");
// 創建SecurityManager
SecurityManager securityManager = factory.getInstance();
// 將securityManager設置當前的運行環境中
SecurityUtils.setSecurityManager(securityManager);
// 從SecurityUtils裏邊創建一個subject
Subject subject = SecurityUtils.getSubject();
// 在認證提交前準備token(令牌)
// 這裏的賬號和密碼 將來是由用戶輸入進去
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
"111111");
try {
// 執行認證提交
subject.login(token);
} catch (AuthenticationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 是否認證通過
boolean isAuthenticated = subject.isAuthenticated();
System.out.println("是否認證通過:" + isAuthenticated);
}