1. 程式人生 > >shiro在SSM以及與Springboot+Mybatis中的實踐

shiro在SSM以及與Springboot+Mybatis中的實踐

公司專案使用的是前後端分離的方式編寫專案,並且我們後臺專案打包是打包成jar,所以沒有web.xml這個檔案。
公司是使用SpringBoot+Mybatis+Shiro的框架編寫,使用Maven這個外掛構建專案的。
下面是我根據公司專案總結的如何使用java檔案來配置Shiro環境的。
參考文件如下:
     SSM中shiro的實現:
            http://blog.csdn.net/xiangwanpeng/article/details/54793768
      多Realm的學習:   
            http://blog.csdn.net/u010837612/article/details/46053249  

1、在POM.xml中加入依賴

 1.1首先我們需要了解一下shiro常用jar包含義:
           核心包shiro-core
           Web相關包shiro-web
           快取包shiro-ehcache
           與spring整合包shiro-spring
           Ehcache快取核心包ehcache-core
           Shiro自身日誌包slf4j-jdk14





1.2加入依賴
        <dependency>  
            <groupId
>
org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.4.0</version> </dependency> <!--這個裡面就已經會有web這個jar包了--> <dependency> <groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.4.0</version> </dependency>

2、編寫核心檔案

首先我們需要了解一下Realm的含義:
    shiro要進行身份驗證,就要從realm中獲取相應的身份資訊來進行驗證,簡單來說,我們可以自行定義realm,
    在realm中,從資料庫獲取身份資訊,然後和 使用者輸入的身份資訊進行匹配。這一切都由我們自己來定義。
多Realm是由於多個不同的身份造成的
2.1在有web.xml的情況下編寫
      2.1.1在web.xml中配置shiroFilter
    <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>
      2.1.2編寫自己的身份認證以及搜權(這個是通過繼承AuthorizingRealm實現的)
        是通過doGetAuthorizationInfo授予許可權和AuthenticationToken登入身份驗證兩個方法實現的
@Component
public class MemberAccountRealm extends AuthorizingRealm {

    @Autowired
    private MemberAccountService memberAccountService;

    /**
     * 授權(驗證許可權時呼叫)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        // 獲取登入時輸入的使用者名稱  
        MemberAccount memberAccount = (MemberAccount) principals.getPrimaryPrincipal();
        String account = memberAccount.getAccount();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        // 使用者角色授權
        addRole(account, info);
        // 使用者許可權授權
        addPermisson(account, info);

        return info;
    }

    /**
     * 使用者身份認證(登入時呼叫)
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws RuntimeException{

        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;

        // 獲取前臺使用者輸入的使用者名稱和密碼
        String telephone = (String) token.getPrincipal();

        // 查詢使用者資訊
        MemberAccount memberAccount=null;
        try {
            memberAccount = this.memberAccountService.queryMemberAccount(telephone, IsDelete.IS_NOT_DELETE);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

        // 獲取密碼並加密
        String password = ShiroUtils.passwordEncrypr(String.valueOf(usernamePasswordToken.getPassword()), String.valueOf(memberAccount.getMemberId()));

        // 重新放到token中
        usernamePasswordToken.setPassword(password.toCharArray());

        // 若存在,將此使用者存放到登入認證中進行密碼匹配
        return new SimpleAuthenticationInfo(memberAccount,memberAccount.getPassword(), ByteSource.Util.bytes(telephone),getName());
    }
    /**
     * 
     * 模擬授予許可權
     * @param loginName
     * @param info
     */
    private void addPermisson(String loginName, SimpleAuthorizationInfo info) {

        List<String> permissonList = new ArrayList<String>();
        permissonList.add("sys:user:delete");
        permissonList.add("sys:user:update");
        permissonList.add("sys:user:save");
        permissonList.add("sys:user:list");
        //使用者角色列表
        Set<String> permissonSet = new HashSet<String>();     
        for ( String perm : permissonList ) {
            if (StringUtils.isBlank(perm)) {
                continue;
            }
            permissonSet.addAll(Arrays.asList(perm.trim().split(",")));
        }
        info.setStringPermissions(permissonSet);
    }

    /**
     * 
     * 使用者角色授權
     * @param loginName
     * @param info
     */
    private void addRole(String loginName, SimpleAuthorizationInfo info) {
        List<String> RoleList = new ArrayList<String>();
        RoleList.add("admin");
        //使用者角色列表
        Set<String> RoleSet = new HashSet<String>();
        for ( String role : RoleList ) {
            if (StringUtils.isBlank(role)) {
                continue;
            }
            RoleSet.addAll(Arrays.asList(role.trim().split(",")));
        }

        info.setRoles(RoleSet);
    }


}
      2.1.3可以自定義一個密碼驗證方法
public class CredentialsMatcher extends SimpleCredentialsMatcher{

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        UsernamePasswordToken utoken = (UsernamePasswordToken) token;
        // 獲得使用者輸入的密碼:(可以採用加鹽(salt)的方式去檢驗)
        String inPassword = new String(utoken.getPassword());
        // 獲得資料庫中的密碼
        String dbPassword = (String) info.getCredentials();
        // 進行密碼的比對
        return this.equals(inPassword, dbPassword);
    }

}
      2.1.4在src目錄下家裡ehcache.xml,檔案內容如下:
<!--
  ~ Hibernate, Relational Persistence for Idiomatic Java
  ~
  ~ License: GNU Lesser General Public License (LGPL), version 2.1 or later.
  ~ See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
  -->
<ehcache>

    <!-- Sets the path to the directory where cache .data files are created.

         If the path is a Java System Property it is replaced by
         its value in the running VM.

         The following properties are translated:
         user.home - User's home directory
         user.dir - User's current working directory
         java.io.tmpdir - Default temp file path -->
    <diskStore path="./target/tmp"/>


    <!--Default Cache configuration. These will applied to caches programmatically created through
        the CacheManager.

        The following attributes are required for defaultCache:

        maxInMemory       - Sets the maximum number of objects that will be created in memory
        eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
                            is never expired.
        timeToIdleSeconds - Sets the time to idle for an element beforeQuery it expires. Is only used
                            if the element is not eternal. Idle time is now - last accessed time
        timeToLiveSeconds - Sets the time to live for an element beforeQuery it expires. Is only used
                            if the element is not eternal. TTL is now - creation time
        overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
                            has reached the maxInMemory limit.

        -->
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />

    <!--Predefined caches.  Add your cache configuration settings here.
        If you do not have a configuration for your cache a WARNING will be issued when the
        CacheManager starts

        The following attributes are required for defaultCache:

        name              - Sets the name of the cache. This is used to identify the cache. It must be unique.
        maxInMemory       - Sets the maximum number of objects that will be created in memory
        eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
                            is never expired.
        timeToIdleSeconds - Sets the time to idle for an element beforeQuery it expires. Is only used
                            if the element is not eternal. Idle time is now - last accessed time
        timeToLiveSeconds - Sets the time to live for an element beforeQuery it expires. Is only used
                            if the element is not eternal. TTL is now - creation time
        overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
                            has reached the maxInMemory limit.

        -->

    <!-- Sample cache named sampleCache1
        This cache contains a maximum in memory of 10000 elements, and will expire
        an element if it is idle for more than 5 minutes and lives for more than
        10 minutes.

        If there are more than 10000 elements it will overflow to the
        disk cache, which in this configuration will go to wherever java.io.tmp is
        defined on your system. On a standard Linux system this will be /tmp"
        -->
    <cache name="sampleCache1"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />

    <!-- Sample cache named sampleCache2
        This cache contains 1000 elements. Elements will always be held in memory.
        They are not expired. -->
    <cache name="sampleCache2"
        maxElementsInMemory="1000"
        eternal="true"
        timeToIdleSeconds="0"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        /> -->

    <!-- Place configuration for your caches following -->

</ehcache>
      2.1.5在spring的配置檔案中加入以下程式碼:
     <!-- shiro start -->
    <!-- 1. 配置SecurityManager -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager" />
        <!--這個是配置的多Realm的-->
        <!--<property name="authenticator" ref="authenticator"></property>-->
        <!-- 可以配置多個Realm,其實會把realms屬性賦值給ModularRealmAuthenticator的realms屬性 -->
        <property name="realms">
            <list>
                <ref bean="authRealm" />
            </list>
        </property>
    </bean>

    <!-- 2. 配置CacheManager -->
    <!-- 2.1 需要加入ehcache的jar包及配置檔案 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />
    </bean>

    <!-- 3. 配置Realm -->
    <!-- 3.1 直接配置繼承了org.apache.shiro.realm.AuthorizingRealm的bean -->
    <bean id="authRealm" class="com.ysw.mes.common.shiro.MemberAccountRealm">
        <!-- 配置密碼匹配器 -->
        <!-- 非自定義的寫法
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <!-- 加密演算法為MD5 -->
                <property name="hashAlgorithmName" value="MD5"></property>
                <!-- 加密次數 -->
                <property name="hashIterations" value="1024"></property>
            </bean>
        </property>
         -->
         <!-- 自定義的寫法 -->
         <property name="credentialsMatcher" ref="credentialsMatcher">
    </bean>
    <!-- 自定義的密碼匹配器 -->
    <bean id="credentialsMatcher" class="com.ysw.mes.common.shiro.CredentialsMatcher"/>

    <!-- 4. 配置LifecycleBeanPostProcessor,可以自定義地來呼叫配置在Spring IOC容器中shiro bean的生命週期方法 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

    <!-- 5. 使能夠在IOC容器中使用shiro的註解,但必須在配置了LifecycleBeanPostProcessor之後才可以使用 -->
    <bean
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
        depends-on="lifecycleBeanPostProcessor" />
    <bean
        class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>

    <!-- 6. 配置ShiroFilter -->
    <!-- 6.1 id必須和web.xml中配置的DelegatingFilterProxy的<filter-name>一致。 如果不一致,會丟擲NoSuchBeanDefinitionException異常,因為shiro會在IOC容器中查詢名稱和<filter-name> 
        值一致的filter bean -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/login.jsp" />
        <!--<property name="successUrl" value="/home" />-->
        <!-- 配置哪些頁面需要受保護,以及訪問這些頁面需要的許可權
        <property name="filterChainDefinitions">
            <value>
                <!-- 第一次匹配優先的原則 -->
                /jsp/login.jsp* = anon
                /loginUser = anon
                /logout* = anon
                /jsp/error.jsp* = anon
                /jsp/index.jsp* = authc
                /* = authc
                /*.* = authc
            </value>
        </property>
         -->
    </bean>

    <!-- 7. 配置ModularRealmAuthenticator,可以實現多Realm認證 (公司專案並沒有使用到)
    <bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
        <!-- 配置認證策略,只要有一個Realm認證成功即可,並且返回所有認證成功資訊 -->
        <property name="authenticationStrategy">
            <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
        </property>
    </bean>
    -->
    <!-- shiro end -->
2.2在無web.xml的情況下編寫(在這裡我就不配置cache了,相信大家會寫的哈)
    2.2.1編寫同上的2.1.2、2.1.3
    2.2.2編寫一個Shiro的配置檔案如下(相當於上面的 2.1.1和2.1.5的作用)
import java.util.LinkedHashMap;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.ysw.mes.common.shiro.CredentialsMatcher;
import com.ysw.mes.common.shiro.MemberAccountRealm;

/**
 * 這個是shiro的配置檔案
 * 這個檔案會使用到
 *  CredentialsMatcher和MemberAccountRealm這兩個檔案
 * @author admin
 * 
 * 
 * 學習shiro的網址有:
 *      http://blog.csdn.net/xiangwanpeng/article/details/54793768
 * 
 *      其中對於多Realm的解釋:
 * 
 *
 */
@Configuration
public class ShiroConfiguration {

    /**
     * 
     * @param manager
     * @return
     */
    @Bean(name="shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);
        // 配置登入的url和登入成功的url
        bean.setLoginUrl("/toLogin");
//      bean.setSuccessUrl("/home");
        // 配置訪問許可權
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
//        filterChainDefinitionMap.put("/jsp/login.jsp*", "anon"); //表示可以匿名訪問
//        filterChainDefinitionMap.put("/loginUser", "anon"); 
//        filterChainDefinitionMap.put("/logout*","anon");
//        filterChainDefinitionMap.put("/jsp/error.jsp*","anon");
//        filterChainDefinitionMap.put("/jsp/index.jsp*","authc");
//        filterChainDefinitionMap.put("/*", "authc");//表示需要認證才可以訪問
//        filterChainDefinitionMap.put("/**", "authc");//表示需要認證才可以訪問
//        filterChainDefinitionMap.put("/*.*", "authc");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }

    //配置核心安全事務管理器
    @Bean(name="securityManager")
    public SecurityManager securityManager(@Qualifier("authRealm") MemberAccountRealm authRealm) {
        System.err.println("--------------shiro已經載入----------------");
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(authRealm);
        return manager;
    }
    @
    /**
     * 配置自定義的許可權以及身份認證器
     * 以及將自定義的密碼比較器注入進去
     * @param matcher
     * @return
     */
    @Bean(name="authRealm")
    public MemberAccountRealm authRealm(@Qualifier("credentialsMatcher") CredentialsMatcher matcher) {
        MemberAccountRealm authRealm=new MemberAccountRealm();
        authRealm.setCredentialsMatcher(matcher);
        return authRealm;
    }

    /**
     * 使用的是自定義的密碼比較器CredentialsMatcher
     * 
     * @return CredentialsMatcher是自定義類
     */
    @Bean(name="credentialsMatcher")
    public CredentialsMatcher credentialsMatcher() {
        return new CredentialsMatcher();
    }

    /**
     * 配置LifecycleBeanPostProcessor,可以自定義地來呼叫配置在Spring IOC容器中shiro bean的生命週期方法
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }
    /**
     * 使能夠在IOC容器中使用shiro的註解,但必須在配置了LifecycleBeanPostProcessor之後才可以使用
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator creator=new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager manager) {
        AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(manager);
        return advisor;
    }
}

在寫完上面內容後,再看一次系統後,發現登入驗證的順序原來是這樣的
1、由於在shiro的配置檔案中配置了
這裡寫圖片描述
這裡寫圖片描述
2、首先訪問/toLogin這個對應的方法
這裡寫圖片描述
3、由於subject.login(token)這個,會呼叫到
這裡寫圖片描述
4、將資料塞進token和info這兩個實體之後進行比較
這裡寫圖片描述