1. 程式人生 > >Shiro安全框架入門使用方法

Shiro安全框架入門使用方法

框架介紹

Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼學和會話管理。使用Shiro的易於理解的API,您可以快速、輕鬆地獲得任
何應用程式,從最小的移動應用程式到最大的網路和企業應用程式。

Shrio的主要功能:

  • Authentication:使用者認證(登入)
  • Authorization:許可權控制
  • Session Management:會話管理
  • Cryptography:資料加密
  • Web Support:支援web的API
  • Caching:快取
  • Concurrency:支援多執行緒應用程式
  • Testing:測試的支援
  • “Run As”:假設一個使用者為另一個使用者的身份
  • “Remember Me”:在Session中儲存使用者身份

基本原理

Shiro的基本架構:

Shiro有三個核心的概念:Subject、SecurityManager和Realms。

  • Subject:Subject實質上是一個當前執行使用者的特定的安全“檢視”,開發者所寫的應用程式碼就通過Subject與Shiro框架進行互動。所有Subject例項都必須繫結到一個SecurityManager上,當使用一個Subject例項時,Subject例項會和SecurityManager進行互動,完成相應操作。

  • SecurityManager:SecurityManager是Shiro的核心部分,作為一種“保護傘”物件來協調內部安全元件共同構成一個物件圖。開發人員並不直接操作SecurityManager,而是通過Subject來操作SecurityManager來完成各種安全相關操作。

  • Realms:Realms擔當Shiro和應用程式的安全資料之間的“橋樑”或“聯結器”。從本質來講,Realm是一個特定安全的DAO,Realm中封裝了資料操作的模組和使用者自定義的認證匹配過程。SecurityManager可能配置多個Realms,但至少要有一個。

使用方法

注:該示例基於一個Maven管理的SSH專案

1.在Maven中新增Shiro依賴

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-all</artifactId>
    <version>1.2.3</version>
</dependency>

注:Shiro官方現在不推薦這種用法,因為可能會導致Maven執行錯誤,詳見

2.在web.xml新增核心過濾器,且必須放在Struts2核心過濾器前面

<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

3.在Spring配置檔案中新增如下程式碼,且放在事務管理器之前

<!--Shiro安全框架產生代理子類的方式: 使用cglib方式,放在事務管理器之前-->
<aop:aspectj-autoproxy proxy-target-class="true" />

4.在Spring配置檔案中配置Shiro,建議單獨出一個applicationContext-shiro.xml進行配置(編寫完成後記得在總的配置檔案中引入該配置檔案)

applicationContext-shiro.xml

<?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:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans    
    http://www.springframework.org/schema/beans/spring-beans.xsd    
    http://www.springframework.org/schema/aop    
    http://www.springframework.org/schema/aop/spring-aop.xsd    
    http://www.springframework.org/schema/tx    
    http://www.springframework.org/schema/tx/spring-tx.xsd    
    http://www.springframework.org/schema/context    
    http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- SecurityManager配置 -->
    <!-- 配置Realm域 -->
    <!-- 密碼比較器 -->
    <!-- 配置快取:ehcache快取 -->

    <!-- 安全管理 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 引用自定義的realm -->
        <property name="realm" ref="authRealm"/>
        <!-- 快取 -->
        <property name="cacheManager" ref="shiroEhcacheManager"/>
    </bean>

    <!-- 自定義許可權認證 -->
    <bean id="authRealm" class="com.songzheng.demo.shiro.AuthRealm">
        <property name="userService" ref="userService"/>
        <!-- 自定義密碼加密演算法  -->
        <property name="credentialsMatcher" ref="passwordMatcher"/>
    </bean>

    <!-- 設定密碼加密策略 md5hash -->
    <bean id="passwordMatcher" class="com.songzheng.demo.shiro.DemoCredentialsMatcher"/>

    <!-- filter-name這個名字的值來自於web.xml中filter的名字 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!--登入頁面  -->
        <property name="loginUrl" value="/index.jsp"></property>
        <!-- 登入成功後 -->      
        <property name="successUrl" value="/home.action"></property>
        <property name="filterChainDefinitions">
            <!-- /**代表下面的多級目錄也過濾 -->
            <value>
                /index.jsp* = anon
                /home* = anon
                /sysadmin/login/login.jsp* = anon
                /sysadmin/login/logout.jsp* = anon
                /login* = anon
                /logout* = anon
                /components/** = anon
                /css/** = anon
                /images/** = anon
                /js/** = anon
                /make/** = anon
                /skin/** = anon
                /stat/** = anon
                /ufiles/** = anon
                /validator/** = anon
                /resource/** = anon
                /** = authc
                /*.* = authc
            </value>
        </property>
    </bean>

    <!-- 使用者授權/認證資訊Cache, 採用EhCache  快取 -->
    <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
    </bean>

    <!-- 保證實現了Shiro內部lifecycle函式的bean執行 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- 生成代理,通過代理進行控制 -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor">
        <property name="proxyTargetClass" value="true"/>
    </bean>

    <!-- 安全管理器 -->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

</beans>

在這裡配置了核心的SecurityManager,SecurityManager中注入了realm和cacheManager,因此需要配置realm和cacheManager,Realm是自定義的,其中注入了userService,用來進行查詢資料庫資料的相關操作,還注入了credentialsMatcher屬性,這個是開發者自定義的比較器。配置CacheManager時,使用了ehcache,在最後也進行了配置,並且編寫了ehcache的配置檔案,這些操作在下述步驟進行。

在shiroFilter的配置中配置了要過濾的目錄,其中一個*號表示匹配當前目錄後的引數,兩個*號表示匹配該目錄及其子目錄及引數。等號後面是Shiro各類過濾器的簡稱,anon表示匿名過濾器,表示可以匿名訪問這些資源,authc表示需要驗證使用者身份才能訪問,還有8種過濾器,在此就不再詳述了。

5.Ehcache的配置檔案

ehcache-shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">

    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="false"
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120"/>

</ehcache>

6.編寫自定義的credentialsMatcher

DemoCredentialsMatcher.java

public class DemoCredentialsMatcher extends SimpleCredentialsMatcher {

    /**
     * 密碼比較的規則
     * token:使用者在介面輸入的使用者名稱和密碼
     * info: 從資料庫中得到的加密資料
     */
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        // 獲取使用者輸入的密碼,並加密
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String md5Pwd = Encrypt.md5(new String(upToken.getPassword()), upToken.getUsername());

        // 獲取資料庫中的加密密碼
        String pwd = (String) info.getCredentials();

        // 返回比較結果
        return this.equals(md5Pwd, pwd);
    }

}

Encrypt是一個進行MD5加密的工具類

Encrypt.java

public class Encrypt {
    public static String md5(String password, String salt) {
        return new Md5Hash(password, salt, 2).toString();
    }
}

7.編寫自定義的realm

AuthRealm.java

public class AuthRealm extends AuthorizingRealm {

    private IUserService userService;

    public void setUserService(IUserService userService) {
        this.userService = userService;
    }

    /**
     * 授權,當jsp頁面遇到shiro標籤會執行該方法
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
        System.out.println("授權");
        User user = (User) pc.fromRealm(this.getName()).iterator().next();  // 根據realm名字找到對應的realm

        List<String> permissions = new ArrayList<String>();

        Set<Role> roles = user.getRoles();
        for (Role role : roles) {
            Set<Module> modules = role.getModules();
            for (Module module : modules) {
                permissions.add(module.getName());
            }
        }

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissions);     // 新增使用者的許可權

        return info;
    }

    /**
     * 認證
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("認證");
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;

        List<User> list = userService.find("from User u where u.userName = ?", User.class, new String[]{upToken.getUsername()});

        if (list != null && list.size() > 0) {
            User user = list.get(0);
            AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
            return info;
        }

        return null;    // 返回null丟擲異常
    }

}

8.登入操作

try {
    // 1、得到Subject
    Subject subject = SecurityUtils.getSubject();

    // 2、呼叫登入方法---AuthRealm#doGetAuthenticationInfo()
    subject.login(new UsernamePasswordToken(username, password));

    // 3、登入成功
    User user = (User) subject.getPrincipal();

    // 4、放入session
    session.put(SysConstant.CURRENT_USER_INFO, user);
} catch (Exception e) {
    e.printStackTrace();
    request.put("errorInfo", "使用者名稱或密碼錯誤!");
    return "login";
}

return SUCCESS;

在Shiro框架中,如果登陸失敗,則會丟擲異常,所有在使用Subject的程式碼外要使用try-catch,當捕獲異常,表示使用者身份驗證失敗。