1. 程式人生 > >Shiro安全框架入門

Shiro安全框架入門

指定端口 名稱 getc lte zed w3cschool trace aci ali

完整學習網址: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安全框架入門