Shiro安全框架入門
完整學習網址:https://www.w3cschool.cn/shiro
一、Shiro簡介
1、Shiro的概念
Apache Shiro是一個強大易用的Java安全框架,提供了認證、授權、加密和會話管理等功能。
認證(Authentication):用戶身份識別,常被稱為用戶“登錄”,判斷用戶是否登陸,如果未登陸,則攔截其請求。優點:可以實現單點登錄,多個子系統登錄一個其他子系統也自動登錄。(如登錄了淘寶,天貓也自動登錄了)
授權(Authorization):訪問控制。當用戶登陸後,判斷其身份是否有權限訪問相應的資源,如果沒有權限則攔截
密碼加密(Cryptography):保護或隱藏數據防止被偷竊。將MD5進行二次封裝,讓其更加容易使用。註意MD5不可逆運算
會話管理(Session Management)
Shiro的三大核心組件
Subject:正與系統進行交互的人,或某一個第三方服務。所有Subject實例都被綁定到(且這是必須的)一個SecurityManager上。
SecurityManager:Shiro架構的心臟,典型的Facade模式。用來協調內部各安全組件,管理內部組件實例,並通過它來提供安全管理的各種服務。當Shiro與一個Subject進行交互時,實質上是幕後的SecurityManager處理所有繁重的Subject安全操作。
Realms:本質上是一個特定安全的DAO。當配置Shiro時,必須指定至少一個Realm用來進行身份驗證和/或授權。Shiro提供了多種可用的Realms來獲取安全相關的數據。如關系數據庫(JDBC),INI及屬性文件等。可以定義自己Realm實現來代表自定義的數據源。
Shiro 完整架構圖
2、Shiro內置過濾器
Shiro的內置過濾器分為兩組:
認證過濾器:anon(不認證也可以訪問),authcBasic, authc(必須認證後才可訪問)
授權過濾器:perms(指定資源需要哪些權限才可以訪問),Roles, ssl, rest, port
過濾器名稱 |
過濾器類 |
描述 |
anon |
org.apache.shiro.web.filter.authc.AnonymousFilter |
匿名過濾器 |
authc |
org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
如果繼續操作,需要做對應的表單驗證否則不能通過 |
authcBasic |
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
基本http驗證過濾,如果不通過,跳轉登錄頁 |
perms |
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
權限過濾器 |
port |
org.apache.shiro.web.filter.authz.PortFilter |
端口過濾器,可以設置是否是指定端口如果不是跳轉到登錄頁面 |
rest |
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
http方法過濾器,可以指定如post不能進行訪問等 |
roles |
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
角色過濾器,判斷當前用戶是否指定角色 |
ssl |
org.apache.shiro.web.filter.authz.SslFilter |
請求需要通過ssl,如果不是跳轉登錄頁 |
user |
org.apache.shiro.web.filter.authc.UserFilter |
如果訪問一個已知用戶,比如記住我功能,走這個過濾器 |
二、基本使用
配合Demo說明基本使用方法
項目環境:Eclipse+SSH
1、ERP整合Shiro
1)添加依賴
在erp_parent父工程的pom.xml添加依賴
<properties> <shiro.ver>1.2.3</shiro.ver> </properties> <dependencies> <!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro.ver}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>${shiro.ver}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.ver}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-aspectj</artifactId> <version>${shiro.ver}</version> </dependency> </dependencies>
2)配置web.xml, 添加過濾器代理DelegatingFilterProxy,要放在struts2的核心過濾器之前
<!-- 配置shiro的過濾器代理DelegatingFilterProxy --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>*.action</url-pattern> <url-pattern>*.html</url-pattern> <url-pattern>*</url-pattern> </filter-mapping>
Spring提供的一個簡便的過濾器處理方案,它將具體的操作交給內部的Filter對象delegate去處理,而這個delegate對象通過Spring的IOC容器獲取,這裏采用的是Spring的FactorBean的方式獲取這個對象。雖然配置了這一個filter,但是它並沒做任何實際的工作,而是把這個工作交由Spring容器中一個bean的id為shiroFilter的類,即ShiroFilterFactoryBean。
3)添加shiro核心控制器的spring配置文件applicationContext_shiro.xml在erp_web的資源目錄下(中間省略了部分業務相關的配置)
註意過濾鏈的順序,匿名-->授權-->認證
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd "> <!-- shiro的過濾工廠,相當默認的加載了9個過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- 安全管理器,shiro核心組件(大腦) Facade模式 --> <property name="securityManager" ref="securityManager" /> <property name="filters"> <map> <entry key="perms" value-ref="erpAuthorizationFilter"></entry> </map> </property> <!-- 認證相關配置:用戶如果沒有登陸,當他在訪問資源的時候,就會自動跳轉到登陸的頁面 --> <property name="loginUrl" value="/login.html"></property> <!-- 授權相關配置:當用戶沒有訪問某項資源權限的時候,跳轉到該頁面 --> <property name="unauthorizedUrl" value="/error.html"></property> <!-- 過濾鏈的定義:定義URL訪問的時候對應的認證或授權時處理的過濾器 --> <property name="filterChainDefinitions"> <value> /error.html = anon /login_*.action = anon /login_* = anon /emp_*=perms["用戶角色設置","重置密碼"] /goodstype.html=perms["商品類型"] /goodstype_*=perms["商品類型"] /goods.html=perms["商品"] /goods_*=perms["商品"] /orders.html=perms["采購訂單查詢","采購訂單申請","采購訂單審核","采購訂單確認","采購訂單入庫","我的采購訂單","銷售訂單查詢","銷售訂單錄入","銷售訂單出庫"] /orders_*=perms["采購訂單查詢","采購訂單申請","采購訂單審核","采購訂單確認","采購訂單入庫","我的采購訂單","銷售訂單查詢","銷售訂單錄入","銷售訂單出庫"] /report_*.html=perms["銷售統計報表","銷售趨勢報表"] /report_*=perms["銷售統計報表","銷售趨勢報表"] /*.html = authc /*.action = authc /* = authc </value> </property> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="erpRealm"></property> </bean> <!-- 自定義的realm --> <bean id="erpRealm" class="cn.itcast.erp.realm.ErpRealm"> <property name="empBiz" ref="empBiz"></property> </bean> <!-- 自定義的過濾器 --> <bean id="erpAuthorizationFilter" class="cn.itcast.erp.filter.ErpAuthorizationFilter"></bean> </beans>
4)創建error.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>提示信息</title> </head> <body> 尊敬的用戶,您沒有訪問權限! </body> </html>
2、認證功能
1)LoginAction的checkUser方法中使用Subject自帶的login方法
說明:UsernamePasswordToken是AuthenticationToken的子接口的實現類,它是對用戶名和密碼的封裝。
Subject是對當前用戶執行操作的封裝,此處用它來執行登陸的操作。如果用戶名和密碼錯誤,login方法會拋出AuthenticationException異常
public void checkUser() { try { //1. 創建令牌,身份證明 UsernamePasswordToken upt = new UsernamePasswordToken(username,pwd); //2. 獲取主題 subject: 封裝當前用戶的一些操作 Subject subject = SecurityUtils.getSubject(); //3. 執行login subject.login(upt); } catch (Exception e) { e.printStackTrace(); } }
2)自定義Realm
Realm的概念:Realm是Shiro與應用安全數據間的“橋梁”或者“連接器”。實質上是一個安全相關的DAO,它封裝了數據源的連接細節,並在需要時將相關數據提供給Shiro。當配置Shiro時,你必須至少指定一個Realm,用於認證和(或)授權。可以配置多個Realm。
作業流程:使用subject.login方法後,並不會調用登陸的業務層進行登陸的驗證查詢,即不會從數據庫查找登陸的用戶名和密碼是否正確,而是將這項工作交給shiro去完成。
shiro通過Realm找到我們提供的登陸驗證業務,驗證登陸的用戶名和密碼是否正確。因此真正實現登陸驗證的是Realm,而shiro只是去調Realm。也就是說,當對用戶執行認證(登錄)和授權(訪問控制)驗證時,Shiro會從應用配置的Realm中查找用戶及其權限信息。
a)在erp_web子工程下創建包cn.itcast.erp.realm
b)創建ErpRealm類繼承自AuthorizingRealm
public class ErpRealm extends AuthorizingRealm { private IEmpBiz empBiz; public void setEmpBiz(IEmpBiz empBiz) { this.empBiz = empBiz; } /** * 認證 * @return null:認證失敗, AuthenticationInfo實現類,認證成功 */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //通過令牌得到用戶名和密碼 UsernamePasswordToken upt = (UsernamePasswordToken) token; //得到密碼 String pwd = new String(upt.getPassword()); //調用登錄查詢 Emp emp = empBiz.findByUsernameAndPwd(upt.getUsername(), pwd); if(null != emp) { //構造參數1: 主角=登陸用戶 //參數2:授權碼:密碼 //參數3:realm的名稱 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(emp,pwd,getName()); return info; } return null; } }
empBiz.findByUsernameAndPwd方法
/** * 用戶登陸 * @param username * @param pwd * @return */ public Emp findByUsernameAndPwd(String username, String pwd) { //查詢前先加密 pwd = encrypt(pwd, username); return empDao.findByUsernameAndPwd(username, pwd); }
empDao.findByUsernameAndPwd方法
public Emp findByUsernameAndPwd(String username, String pwd){ String hql = "from Emp where username=? and pwd=?"; List<Emp> list = (List<Emp>) this.getHibernateTemplate().find(hql, username, pwd); //能夠匹配上,則返回第一個元素 if(list.size() > 0){ return list.get(0); } //如果登陸名或密碼不正確 return null; }
c)applicationContext_shiro.xml中與自定義ErpRealm相關的配置
<!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="erpRealm"></property> </bean> <!-- 自定義的realm --> <bean id="erpRealm" class="cn.itcast.erp.realm.ErpRealm"> <property name="empBiz" ref="empBiz"></property> </bean>
註意:登錄的action要配置為anon,不然由於配置了/*=authc,會強行跳轉到登陸頁面。
/login_*.action = anon
/login_* = anon
d)LoginAction中的loginOut方法也可以通過subject的getPrincipal方法提取主題對象。
Shiro提供了會話管理機制,實際上自定義的realm認證方法返回值對象中的主角對象就是登陸的用戶,可以通過subject的getPrincipal方法將其提取出來。
/** * 退出登陸 */ public void loginOut() { //ActionContext.getContext().getSession().remove("loginUser"); SecurityUtils.getSubject().logout(); }
3、授權功能
授權就是通過設置規則,指定哪些URL需要哪些權限才可以訪問。
在ErpRealm中新增授權方法作用:告訴shiro當前用戶有什麽權限
在applicationContext_shiro.xml增加配置信息作用:告訴shiro什麽資源有什麽權限才可以訪問
1)授權方法與配置
a) 在ErpRealm中新增授權方法
/** * 授權 */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //獲取當前用戶 Emp emp = (Emp) principals.getPrimaryPrincipal(); //獲取當前登陸用戶的菜單權限 List<Menu> menuList = empBiz.getMenusByEmpuuid(emp.getUuid()); //加入授權 for(Menu m : menuList) { //這裏使用menuname來做授權裏的key值,那麽在配置授權訪問的url=perms[菜單名稱] info.addStringPermission(m.getMenuname()); } return info; }
empBiz.getMenusByEmpuuid方法
/** * 根據員工編號獲取菜單 * @param uuid */ public List<Menu> getMenusByEmpuuid(Long uuid) { return empDao.getMenusByEmpuuid(uuid); }
empDao.getMenusByEmpuuid方法
public List<Menu> getMenusByEmpuuid(Long uuid) { String hql = "select m from Emp e join e.roles r join r.menus m where e.uuid=?"; return (List<Menu>) this.getHibernateTemplate().find(hql, uuid); }
b) applicationContext_shiro.xml中與配置授權控制規則相關的內容
/goodstype.html=perms["商品類型"]
/goodstype_*=perms["商品類型"]
/goods.html=perms["商品"]
/goods_*=perms["商品"]
/store.html=perms["倉庫"]
/store_*=perms["倉庫"]
/orders.html=perms["采購訂單查詢","采購訂單申請","采購訂單審核","采購訂單確認","采購訂單入庫","我的采購訂單","銷售訂單查詢","銷售訂單錄入","銷售訂單出庫"]
/orders_*=perms["采購訂單查詢","采購訂單申請","采購訂單審核","采購訂單確認","采購訂單入庫","我的采購訂單","銷售訂單查詢","銷售訂單錄入","銷售訂單出庫"]
/report_*.html=perms["銷售統計報表","銷售趨勢報表"]
/report_*=perms["銷售統計報表","銷售趨勢報表"]
2)自定義授權過濾器
當一個URL有多個權限需要訪問的時候,如果按下面的方法來配置,系統默認使用的是and關系,即同時具備這兩個權限才可以訪問此URL
/orders.html=perms[“采購訂單查詢”,“采購訂單審核”]
如果想要轉換成or關系,即只要有具備一種就可以訪問此URL,就需要自定義授權過濾器。
a)在erp_web子工程下創建包cn.itcast.erp.filter
b)創建自定義過濾器,繼承自AuthorizationFilter
/** * 自定義授權過濾器 */ public class ErpAuthorizationFilter extends AuthorizationFilter { @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { //獲取主題 Subject subject = getSubject(request,response); //orders.html=perms["采購訂單的查詢","采購訂單的審核","采購訂單的確認","采購訂單的入庫"] //mappedValue="采購訂單的查詢","采購訂單的審核","采購訂單的確認","采購訂單的入庫" String[] perms = (String[]) mappedValue; boolean isPermitted = true; if(null == perms || perms.length == 0){ return isPermitted; } if(null != perms || perms.length > 0){ for(String perm : perms) { //只要有一個權限,就返回true if(subject.isPermitted(perm)) { return true; } } } return false; } }
c)applicationContext_shiro.xml中配置過濾器相關內容
<property name="filters"> <map> <entry key="perms" value-ref="erpAuthorizationFilter"></entry> </map> </property> <!-- 自定義的過濾器 --> <bean id="erpAuthorizationFilter" class="cn.itcast.erp.filter.ErpAuthorizationFilter"></bean>
Shiro安全框架入門