什麼是Shiro
shiro是apache的一個開源框架,是一個權限管理的框架,實現 用戶認證、用戶授權。
spring中有spring security (原名Acegi),是一個權限框架,它和spring依賴過於緊密,沒有shiro使用簡單。
shiro不依賴於spring,shiro不僅可以實現 web應用的權限管理,還可以實現c/s系統,分佈式系統權限管理,shiro屬於輕量框架,越來越多企業項目開始使用shiro。
Shiro架構:
- subject:主體,可以是用戶也可以是程序,主體要訪問系統,系統需要對主體進行認證、授權。
- securityManager:安全管理器,主體進行認證和授權都 是通過securityManager進行。
- authenticator:認證器,主體進行認證最終通過authenticator進行的。
- authorizer:授權器,主體進行授權最終通過authorizer進行的。
- sessionManager:web應用中一般是用web容器對session進行管理,shiro也提供一套session管理的方式。
- SessionDao: 通過SessionDao管理session數據,針對個性化的session數據存儲需要使用sessionDao。
- cache Manager:緩存管理器,主要對session和授權數據進行緩存,比如將授權數據通過cacheManager進行緩存管理,和ehcache整合對緩存數據進行管理。
- realm:域,領域,相當於數據源,通過realm存取認證、授權相關數據。
cryptography:密碼管理,提供了一套加密/解密的組件,方便開發。比如提供常用的散列、加/解密等功能。
- 比如md5散列算法。
爲什麼使用Shiro
我們在使用URL攔截的時候,要將所有的URL都配置起來,繁瑣、不易維護
而我們的Shiro實現系統的權限管理,有效提高開發效率,從而降低開發成本。
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相關的jar包全部導入進去
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.3</version>
</dependency>
Shiro認證流程
通過配置文件創建工廠
// 用戶登陸和退出
@Test
public void testLoginAndLogout() {
// 創建securityManager工廠,通過ini配置文件創建securityManager工廠
Factory<SecurityManager> factory = new IniSecurityManagerFactory(
"classpath:shiro-first.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();
// 是否認證通過
isAuthenticated = subject.isAuthenticated();
System.out.println("是否認證通過:" + isAuthenticated);
}
小結
ModularRealmAuthenticator作用進行認證,需要調用realm查詢用戶信息(在數據庫中存在用戶信息)
ModularRealmAuthenticator進行密碼對比(認證過程)。
realm:需要根據token中的身份信息去查詢數據庫(入門程序使用ini配置文件),如果查到用戶返回認證信息,如果查詢不到返回null。
自定義realm
從第一個認證程序我們可以看見,我們所說的流程,是認證器去找realm去查詢我們相對應的數據。而默認的realm是直接去與配置文件來比對的,一般地,我們在開發中都是讓realm去數據庫中比對。
因此,我們需要自定義realm
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();
// 第二步:根據用戶輸入的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;
}
}
配置realm
需要在shiro-realm.ini配置realm注入到securityManager中。
測試自定義realm
同上邊的入門程序,需要更改ini配置文件路徑:
同上邊的入門程序,需要更改ini配置文件路徑:
Factory<SecurityManager> factory = new IniSecurityManagerFactory(
"classpath:shiro-realm.ini");
散列算法
我們如果知道md5,我們就會知道md5是不可逆的,但是如果設置了一些安全性比較低的密碼:111111…即時是不可逆的,但還是可以通過暴力算法來得到md5對應的明文…
建議對md5進行散列時加salt(鹽),進行加密相當 於對原始密碼+鹽進行散列。\
正常使用時散列方法:
- 在程序中對原始密碼+鹽進行散列,將散列值存儲到數據庫中,並且還要將鹽也要存儲在數據庫中。
測試:
public class MD5Test {
public static void main(String[] args) {
//原始 密碼
String source = "111111";
//鹽
String salt = "qwerty";
//散列次數
int hashIterations = 2;
//上邊散列1次:f3694f162729b7d0254c6e40260bf15c
//上邊散列2次:36f2dfa24d0a9fa97276abbe13e596fc
//構造方法中:
//第一個參數:明文,原始密碼
//第二個參數:鹽,通過使用隨機數
//第三個參數:散列的次數,比如散列兩次,相當 於md5(md5(''))
Md5Hash md5Hash = new Md5Hash(source, salt, hashIterations);
String password_md5 = md5Hash.toString();
System.out.println(password_md5);
//第一個參數:散列算法
SimpleHash simpleHash = new SimpleHash("md5", source, salt, hashIterations);
System.out.println(simpleHash.toString());
}
}
自定義realm支持md5
自定義realm
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 = "f3694f162729b7d0254c6e40260bf15c";
// 從數據庫獲取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;
}
}
配置文件:
測試:
// 自定義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",
"222222");
try {
// 執行認證提交
subject.login(token);
} catch (AuthenticationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 是否認證通過
boolean isAuthenticated = subject.isAuthenticated();
System.out.println("是否認證通過:" + isAuthenticated);
}