1、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存取認證、授權相關數據。(它的主要目的是與數據庫打交道,查詢數據庫中的認證的信息(比如用戶名和密碼),查詢授權的信息(比如權限的code等,所以這里可以理解為調用數據庫查詢一系列的信息,一般情況下在項目中采用自定義的realm,因為不同的業務需求不一樣))
注意:在realm中存儲授權和認證的邏輯。
cryptography:密碼管理,提供了一套加密/解密的組件,方便開發。比如提供常用的散列、加/解密等功能。
比如 md5散列算法。
2、shiro介紹
shiro是apache的一個開源框架,是一個權限管理的框架,實現 用戶認證、用戶授權。
spring中有spring security (原名Acegi),是一個權限框架,它和spring依賴過於緊密,沒有shiro使用簡單。
shiro不依賴於spring,shiro不僅可以實現 web應用的權限管理,還可以實現c/s系統,分布式系統權限管理,shiro屬於輕量框架,越來越多企業項目開始使用shiro。
使用shiro實現系統 的權限管理,有效提高開發效率,從而降低開發成本。
3、認證原理:
1、通過ini配置文件創建securityManager
2、調用subject.login方法主體提交認證,提交的token
3、securityManager進行認證,securityManager最終由ModularRealmAuthenticator進行認證。
4、ModularRealmAuthenticator調用IniRealm(給realm傳入token) 去ini配置文件中查詢用戶信息
5、IniRealm根據輸入的token(UsernamePasswordToken,即這里的token是用戶從頁面輸入的信息)從 shiro-first.ini查詢用戶信息(這里是測試階段,后面都是查詢的數據庫,注入service,調用dao),根據賬號查詢用戶信息(賬號和密碼)
如果查詢到用戶信息,就給ModularRealmAuthenticator返回用戶信息(賬號和密碼)
如果查詢不到,就給ModularRealmAuthenticator返回null
6、ModularRealmAuthenticator接收IniRealm返回Authentication認證信息
如果返回的認證信息是null,ModularRealmAuthenticator拋出異常(org.apache.shiro.authc.UnknownAccountException)
如果返回的認證信息不是null(說明inirealm找到了用戶),對IniRealm返回用戶密碼 (在ini文件中存在)和 token中的密碼 進行對比,如果不一致拋出異常(org.apache.shiro.authc.IncorrectCredentialsException)
小結:
ModularRealmAuthenticator作用進行認證,需要調用realm查詢用戶信息(在數據庫中存在用戶信息)
ModularRealmAuthenticator進行密碼對比(認證過程)。
realm:需要根據token中的身份信息去查詢數據庫(入門程序使用ini配置文件),如果查到用戶返回認證信息,如果查詢不到返回null。
4、 散列算法:
通常需要對密碼 進行散列,常用的有md5、sha,
shiro的散列加密是這樣子的:
建議對md5進行散列時加salt(鹽),進行加密相當 於對原始密碼+鹽進行散列。
即md5+salt(這個鹽一般是隨機鹽,即開發人員給定義隨機的字符串或者數字即可)+散列次數
這里的md5是原始的md5的加密了一次的密碼+隨機鹽,然后對這個新的密碼password=(md5+salt),進行散列:如何進行散列呢:就是多次md5加密md5(md5(md5(md5(password)))),這是4次散列,每次密碼的破解的難度都加大。
正常使用時散列方法:
在程序中對原始密碼+鹽進行散列,將散列值存儲到數據庫中,並且還要將鹽也要存儲在數據庫中。
如果進行密碼對比時,使用相同 方法,將原始密碼+鹽進行散列,進行比對。
5、授權原理
原理:
1、對subject進行授權,調用方法isPermitted("permission串")
2、SecurityManager執行授權,通過ModularRealmAuthorizer執行授權
3、ModularRealmAuthorizer執行realm(自定義的CustomRealm)從數據庫查詢權限數據
調用realm的授權方法:doGetAuthorizationInfo
4、realm從數據庫查詢權限數據,返回ModularRealmAuthorizer
5、ModularRealmAuthorizer調用PermissionResolver進行權限串比對
6、如果比對后,isPermitted中"permission串"在realm查詢到權限數據中,說明用戶訪問permission串有權限,否則 沒有權限,拋出異常。
shiro的授權方式有三種:
(1)—— 編程式:通過寫if/else 授權代碼塊完成:(這種比較少用,一般在項目中采用后兩種)
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有權限
} else {
//無權限
}
(2)—— 注解式:通過在執行的Java方法上放置相應的注解完成:
@RequiresRoles("admin")
public void hello() {
//有權限
}
(3)—— JSP/GSP 標簽:在JSP/GSP 頁面通過相應的標簽完成:
在jsp頁面導入shiro的標簽既可以使用shiro的標簽來進行權限的判斷:
Jsp頁面添加:
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
標簽名稱 |
標簽條件(均是顯示標簽內容) |
<shiro:authenticated> |
登錄之后 |
<shiro:notAuthenticated> |
不在登錄狀態時 |
<shiro:guest> |
用戶在沒有RememberMe時 |
<shiro:user> |
用戶在RememberMe時 |
<shiro:hasAnyRoles name="abc,123" > |
在有abc或者123角色時 |
<shiro:hasRole name="abc"> |
擁有角色abc |
<shiro:lacksRole name="abc"> |
沒有角色abc |
<shiro:hasPermission name="abc"> |
擁有權限資源abc |
<shiro:lacksPermission name="abc"> |
沒有abc權限資源 |
<shiro:principal> |
顯示用戶身份名稱 |
<shiro:principal property="username"/> 顯示用戶身份中的屬性值
<shiro:hasRole name="admin">
<!— 有權限—>
</shiro:hasRole>
6、shiro與項目的整合:
整合無非就是jar包和配置文件:
配置文件:在web.xml中配置filter:
在web系統中,shiro也通過filter進行攔截。filter攔截后將操作權交給spring中配置的filterChain(過慮鏈兒)
shiro提供很多filter。 在web.xml中配置filter
與spring的整合交由spring的容器管理:security manager 、realm、filter都交由spring整合
下面可以看下具體realm自定義使用與application_shiro的內容:
realm:
package cn.project.ssm.shiro; import java.security.acl.Permission; import java.util.ArrayList; import java.util.List; import org.apache.shiro.SecurityUtils; 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.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import com.sun.org.apache.bcel.internal.generic.ACONST_NULL; import cn.project.ssm.pojo.ActiveUser; import cn.project.ssm.pojo.SysPermission; import cn.project.ssm.pojo.SysUser; import cn.project.ssm.service.LoginService; /** * * <p> * Title: CustomRealm * </p> * <p> * Description:自定義realm,實際開發中一般都是自定義realm * </p> * <p> * Company: www.itcast.com * </p> * * @date 2015-3-23下午4:54:47 * @version 1.0 */ public class CustomRealm extends AuthorizingRealm { @Autowired private LoginService loginService; // 設置realm的名稱 @Override public void setName(String name) { super.setName("customRealm"); } //用於認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //第一步:從token中取出用戶名,這個用戶名是用戶在頁面輸入的信息,傳遞給token String userCode=(String) token.getCredentials(); //根據用戶名查詢用戶信息 SysUser sysUser=null; sysUser=loginService.findByUserCode(userCode); if (sysUser==null) { return null; } String password=sysUser.getPassword(); //加鹽 String salt=sysUser.getSalt(); //將用戶身份信息寫入activeUser ActiveUser activeUser=new ActiveUser(); activeUser.setUserid(sysUser.getId()); activeUser.setUsercode(sysUser.getUsercode()); activeUser.setUsername(sysUser.getUsername()); //通過service取出菜單 List<SysPermission> menus= loginService.findmenusByUserId(sysUser.getId()); activeUser.setMenus(menus); //寫到這里我們看到realm其實主要是從數據庫中獲取數據 SimpleAuthenticationInfo simpleAuthenticationInfo=new SimpleAuthenticationInfo(activeUser,password,ByteSource.Util.bytes(salt), this.getName()); return simpleAuthenticationInfo; } //用於授權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) { //從princal中獲取主身份信息,將返回值轉為真實的身份信息,填充到上面認證的身份中 ActiveUser activeUser=(ActiveUser) principal.getPrimaryPrincipal(); //從數據庫中獲取到權限數據 List<SysPermission> permissionsList = loginService.findpermissionByUserId(activeUser.getUserid()); List<String> permissions=new ArrayList<>(); for (SysPermission sysPermission : permissionsList) { permissions.add(sysPermission.getPercode()); } //將集合內容填充認證中 SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addStringPermissions(permissions); return simpleAuthorizationInfo; } //清除緩存 public void clearCached() { PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals(); super.clearCache(principals); } }
配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!-- web.xml中shiro的filter對應的bean --> <!-- Shiro 的Web過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <!-- loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 --> <property name="loginUrl" value="/login.action" /> <!-- 認證成功統一跳轉到first.action,建議不配置,shiro認證成功自動到上一個請求路徑 --> <!-- <property name="successUrl" value="/first.action"/> --> <!-- 通過unauthorizedUrl指定沒有權限操作時跳轉頁面--> <property name="unauthorizedUrl" value="/refuse.jsp" /> <!-- 過慮器鏈定義,從上向下順序執行,一般將/**放在最下邊 --> <property name="filterChainDefinitions"> <value> <!-- 對靜態資源設置訪問,不然都攔截了 --> /images/**=anon /js/**=anon /styles/**=anon <!-- 請求logout.action地址,shiro清除session --> /logout.action=logout <!-- /** = authc所有url都可以認證通過才能訪問 --> /** = authc <!-- /** = anon所有url都可以匿名訪問 --> /** = anon </value> </property> </bean> <!-- securityManager安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="customRealm" /> <!-- 注入緩存管理器 --> <property name="cacheManager" ref="cacheManager"/> </bean> <!-- realm --> <bean id="customRealm" class="cn.project.ssm.shiro.CustomRealm"> <!-- 將憑證匹配器設置到realm中,realm按照憑證匹配器的要求進行散列 --> <property name="credentialsMatcher" ref="credentialsMatcher"/> </bean> <!-- 散列加鹽憑證匹配器 --> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="md5" /> <property name="hashIterations" value="1" /> </bean> <!-- 緩存管理器 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/> </bean> </beans>