Java-Shiro(六):Shiro Realm講解(三)Realm的自定義及應用


本片文章源碼位置:《https://github.com/478632418/springmv_without_web_xml/tree/master/mybaits-test-dynamic-sql》
1)環境整理:

為了把自定義Realm的用法接近於真實場景,本章將會基於SpringMvc+Mybatis+Shiro整合后來展示:在真實環境中如何使用自定義Realm。

為什么要定義Realm?

1)真實環境中往往會有自定義的賬戶管理系統,因此使用IniRealm,PropertiesRealm這些配置文件方式不適用:總不能系統中每新增或刪除一個賬戶,都要去修改配置文件;

2)JdbcRealm也是最可能能湊合使用第一個Realm,但是因為JdbcRealm內部已經內置好了表結構,對於與實際應用也不一定能恰好滿足。

因此,大多應用使用Shiro時,都會自定義Realm來操作認證、授權數據。

SpringMVC+Mybatis+Shiro環境整合:

pom.xml

需要引入shiro相關組件、shiro-spring整合組件、springmvc組件+thymeleaf、mybatis組件、mybatis-spring組件、common包、jdbc driver包、druid等。
包版本信息:

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <org.springframework.version>5.2.0.RELEASE</org.springframework.version>
        <com.alibaba.version>1.1.21</com.alibaba.version>
        <mysql.version>8.0.11</mysql.version>
        <org.mybatis.version>3.4.6</org.mybatis.version>
        <org.mybatis.spring.version>2.0.3</org.mybatis.spring.version>
        <org.aspectj.version>1.9.4</org.aspectj.version>
        <jackson.version>2.10.1</jackson.version>
        <shiro.version>1.4.2</shiro.version>
    </properties>

shiro以及shiro-spring相關依賴:

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-cas</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>${shiro.version}</version>
        </dependency>
View Code

mybatis以及mybatis-spring相關依賴:

        <!--訪問RDBMS-MySQL依賴 -->
        <!--MyBatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${org.mybatis.version}</version>
        </dependency>
        <!-- Mybatis自身實現的Spring整合依賴 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>${org.mybatis.spring.version}</version>
        </dependency>
View Code

springmvc+spring+aop相關+thymeleaf相關依賴:

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>

        <!--AOP aspectjweaver 支持 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${org.aspectj.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${org.aspectj.version}</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.0.9.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.thymeleaf/thymeleaf-spring5 -->
        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring5</artifactId>
            <version>3.0.9.RELEASE</version>
        </dependency>

        <!-- 編譯依賴 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <!--Rest Support支持 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-parameter-names</artifactId>
            <version>${jackson.version}</version>
        </dependency>

        <!--form 設置為enctype="multipart/form-data",多文件上傳,在applicationContext.xml中配置了bean 
            multipartResolver時,需要依賴該包。 -->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.4</version>
        </dependency>

        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.5</version>
        </dependency>
View Code

jdbc driver+druid相關依賴:

        <!--MySql數據庫驅動 -->
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${com.alibaba.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
View Code

其他相關依賴:

        <!--日志支持 -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.26</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.26</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
View Code

另外搭建項目還需要web.xml,而且除了web.xml配置文件外,DispatcherServlet使用springmvc-web.xml;ContextLoaderListener使用springmvc-mybatis.xml & spring-shiro.xml。

Web.xml

web.xml中需要引入shiroFilter(shiro框架過濾驗證器)、multipartFilter(用來上傳文件使用)、hiddenHttpMethodFilter(實現put、heade請求使用)、characterEncodingFilter(后端數據輸出到前端亂碼問題):

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    id="WebApp_ID" version="3.1">
    <display-name>ssms</display-name>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
        <welcome-file>/index</welcome-file>
    </welcome-file-list>
    <!-- 加載spring容器 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext-mybatis.xml,
            classpath:applicationContext-shiro.xml
        </param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Shiro Filter is defined in the spring application context: -->
    <!-- 1. 配置 Shiro 的 shiroFilter.                               <br>
         2. DelegatingFilterProxy 實際上是 Filter 的一個代理對象. 默認情況下, Spring 會到 IOC 容器中查找和 <filter-name> 對應的 filter bean. 
         也可以通過 targetBeanName 的初始化參數來配置 filter bean 的 id. -->
    <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>

    <!-- 文件上傳與下載過濾器:form表單中存在文件時,該過濾器可以處理http請求中的文件,被該過濾器過濾后會用post方法提交, form表單需設為enctype="multipart/form-data" -->
    <!-- 注意:必須放在HiddenHttpMethodFilter過濾器之前 -->
    <filter>
        <filter-name>multipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
        <init-param>
            <param-name>multipartResolverBeanName</param-name>
            <!--spring中配置的id為multipartResolver的解析器 -->
            <param-value>multipartResolver</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>multipartFilter</filter-name>
        <!--<servlet-name>springmvc</servlet-name> -->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 注意:HiddenHttpMethodFilter必須作用於dispatcher前 請求method支持 put 和 delete 必須添加該過濾器 
        作用:可以過濾所有請求,並可以分為四種 使用該過濾器需要在前端頁面加隱藏表單域 <input type="hidden" name="_method" 
        value="請求方式(put/delete)"> post會尋找_method中的請求式是不是put 或者 delete,如果不是 則默認post請求 -->
    <filter>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
        <!--可以通過配置覆蓋默認'_method'值 -->
        <init-param>
            <param-name>methodParam</param-name>
            <param-value>_method</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>hiddenHttpMethodFilter</filter-name>
        <!--servlet為springMvc的servlet名 -->
        <servlet-name>springmvc</servlet-name>
        <!--<url-pattern>/*</url-pattern> -->
    </filter-mapping>

    <!-- 后端數據輸出到前端亂碼問題 -->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- springmvc前端控制器 -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>
View Code

本章重點需要關注shiroFilter這塊過濾器引入到web.xml中:

    <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>

其中shiroFilter這個<filter-name>必須要和applicationContext-shiro.xml中的shiroFilter bean名稱一致,否則會拋出找不到shiroFilter的bean異常。

springmvc-servlet.xml

這個配置文件是<sevlet>DispatcherServlet中需要加載的配置文件:

<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-4.0.xsd 
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.0.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-4.0.xsd ">

    <!-- 開啟controller注解支持 -->
    <!-- 注意事項請參考:http://jinnianshilongnian.iteye.com/blog/1762632 -->
    <context:component-scan base-package="com.dx.test.controller" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice" />
    </context:component-scan>
    <!--使用mvc:annotation-driven代替上邊注解映射器和注解適配器 配置 如果使用mvc:annotation-driven就不用配置上面的
        RequestMappingHandlerMapping和RequestMappingHandlerAdapter-->
    <!-- 使用注解驅動:自動配置處理器映射器與處理器適配器 -->
    <!-- <mvc:annotation-driven /> -->
    <mvc:annotation-driven></mvc:annotation-driven>
    
    <!-- 開啟aop,對類代理 -->
    <aop:config proxy-target-class="true"></aop:config>
    
    <!-- 配置啟用Shiro的注解功能 -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" 
        depends-on="lifecycleBeanPostProcessor">
        <property name="proxyTargetClass" value="true"></property>
    </bean>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>
    
    <!-- 單獨使用jsp視圖解析器時,可以取消掉注釋,同時注釋掉:下邊的‘配置多個視圖解析’配置-->
    <!--
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/view/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    -->
        
    <!-- 使用thymeleaf解析  -->  
    <bean id="templateResolver" class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">  
      <property name="prefix" value="/WEB-INF/templates/" />  
      <!--<property name="suffix" value=".html" />-->  
      <property name="templateMode" value="HTML" />  
      <property name="characterEncoding" value="UTF-8"/>  
      <property name="cacheable" value="false" />  
    </bean>  

    <bean id="templateEngine" class="org.thymeleaf.spring5.SpringTemplateEngine">  
      <property name="templateResolver" ref="templateResolver" />  
    </bean>  
    
    <!--單獨使用thymeleaf視圖引擎時,可以取消掉注釋,同時注釋掉:下邊的‘配置多個視圖解析’配置 -->
    <!--
    <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">  
      <property name="templateEngine" ref="templateEngine" />  
      <property name="characterEncoding" value="UTF-8"/>  
    </bean>
    -->

    <!--  配置多個視圖解析 參考:https://blog.csdn.net/qq_19408473/article/details/71214972-->
    <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="viewResolvers">
            <!--
            此時,
            返回視圖:return "abc.jsp" ,將使用jsp視圖解析器,jsp的視圖模板文件在/WEB-INF/views/下;
            返回視圖:return "abc.html",將使用 thymeleaf視圖解析器,thymeleaf的視圖模板文件在/WEB-INF/templates/下。
            -->
            <list>
                <!--used thymeleaf  -->
                <bean class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
                    <property name="characterEncoding" value="UTF-8"/>
                    <property name="templateEngine" ref="templateEngine" />
                    <property name="viewNames" value="*.html"/>
                    <property name="order" value="2" />
                </bean>
                <!-- used jsp -->
                <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
                    <property name="prefix" value="/WEB-INF/views/"/>
                    <!--<property name="suffix" value=".jsp"/>-->
                    <property name="viewNames" value="*.jsp"/>
                    <property name="order" value="1" />
                </bean>
            </list>
        </property>
    </bean>
    
</beans>
View Code

注意:
1)這兩邊配置了雙視圖引擎:jsp、thymeleaf。
2)返回視圖:return "abc.jsp" ,將使用jsp視圖解析器,jsp的視圖模板文件在/WEB-INF/views/下;
3)返回視圖:return "abc.html",將使用 thymeleaf視圖解析器,thymeleaf的視圖模板文件在/WEB-INF/templates/下。

applicationContext-mybatis.xml

該配置文件時ContextLoaderListener需要加載的配置文件之一,目的實現mybatis集成到spring框架中。

<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-4.0.xsd 
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-4.0.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-4.0.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-4.0.xsd ">
        
    <!--掃描Service、Dao里面的注解,這里沒有定義service-->
    <context:component-scan base-package="com.dx.test.dao"></context:component-scan>
    
    <!-- 文件上傳注意id -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 配置默認編碼 -->
        <property name="defaultEncoding" value="utf-8"></property>
        <!-- 配置文件上傳的大小 -->
        <property name="maxUploadSize" value="1048576"></property>
    </bean>

    <!-- 數據庫連接池配置文件Dao層 -->
    <!-- 加載配置文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 數據庫連接池,使用dbcp -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${driver}" />
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
        <property name="maxActive" value="10"/>
        <property name="maxIdle" value="5"/>
    </bean>
    <!-- sqlSessionFactory配置 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
        <property name="configLocation" value="classpath:mybatisConfig.xml" />
        <!-- 掃描entity包 使用別名 -->
        <!-- <property name="typeAliasesPackage" value="com.dx.test.model" /> -->
        <!-- 掃描sql配置文件:mapper需要的xml文件 -->
        <property name="mapperLocations" value="classpath:*dao/*.xml" />
    </bean>

    <!-- 4.配置掃描Dao接口包,動態實現Dao接口,注入到spring容器中 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 注入sqlSessionFactory -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!-- 給出需要掃描Dao接口包 -->
        <property name="basePackage" value="com.dx.test.dao" />
    </bean>

    <!-- 事務管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>    
    </bean>
    
 </beans>
View Code

該配置文件主要配置了以下幾項內容:

1)component-scan:用來掃描 dao/service等bean,當然他們需要在類上注解了@Service/@Repository/@Mapper等;

2)multipartResolver上傳文件使用的文件解析器bean;

3)mybatis需要依賴的數據庫連接池dataSource;

4)mybatis的sqlSessionFactory bean,配置時需要sqlSessionFactory與dataSource關聯,而且可以指定“配置MyBaties全局配置文件:mybatisConfig.xml”等信息;

5)配置掃描Dao接口包,動態實現Dao接口,注入到spring容器中;

6)事務管理器transactionManager。

mybatisConfig.xml是mybatis的基本配置:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--配置全局屬性-->
    <settings>
        <!-- 打開延遲加載的開關 -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 將積極加載改為消極加載(即按需加載) -->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!-- 打開全局緩存開關(二級緩存)默認值就是 true -->
        <setting name="cacheEnabled" value="true"/>
        <!--使用jdbc的getGeneratekeys獲取自增主鍵值-->
        <setting name="useGeneratedKeys" value="true"/>
        <!--使用列別名替換別名  默認true select name as title form table; -->
        <setting name="useColumnLabel" value="true"/>
        <!--開啟駝峰命名轉換-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!--打印sql日志-->
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>
    
    <!-- 引用db.properties配置文件 -->
    <!-- 
    <properties resource="jdbc.properties"/> 
    <typeAliases>
        <package name="com.dx.test.model"/>
    </typeAliases>
    -->
    <!-- 對事務的管理和連接池的配置 -->
    <!-- 
    <environments default="mysql_jdbc">
        <environment id="oracle_jdbc">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${name}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
        <environment id="mysql_jdbc">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${name}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    -->
    
    <!--
    <mappers>
        <mapper resource="resources/mapper/TaskAutoExecutePlanMapper.xml"/>
    </mappers>
    -->
<!--<mappers>
        <mapper class="com.dx.test.dao.LogMapper"></mapper>
        <mapper class="com.dx.test.dao.SysUserMapper"></mapper>
        <mapper class="com.dx.test.dao.SysRoleMapper"></mapper>
        <mapper class="com.dx.test.dao.SysUserRoleMapper"></mapper>
        <mapper class="com.dx.test.dao.SysPermissionMapper"></mapper>
        <mapper class="com.dx.test.dao.SysRolePermissionMapper"></mapper>
    </mappers>-->
</configuration>
View Code

jdbc.properties是mybatis中配置數據連接信息:

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
username=root
password=123456

applicationContext-shiro.xml

applicationContext-shiro.xml是ContextLoaderListener需要配置的另外一個文件,配置它的目的,注入shiroFilter bean,使得它可以在web.xml注入shiroFilter使用。總之,是為了試下shiro來監聽請求,過濾請求。

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

    <!-- 
    <context:component-scan base-package="com.dx.test.shiro"/>
     -->
    <!-- 憑證匹配器 -->
    <bean id="credentialsMatcher"
        class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <!-- 加密算法 -->
        <property name="hashAlgorithmName" value="md5"></property>
        <!-- 迭代次數 -->
        <property name="hashIterations" value="8"></property>
    </bean>
    <!-- 配置自定義Realm -->
    <bean id="myRealm" class="com.dx.test.shiro.MyRealm">
        <!-- 將憑證匹配器設置到realm中,realm按照憑證匹配器的要求進行散列 -->
        <property name="credentialsMatcher" ref="credentialsMatcher"></property>
    </bean>
    <!-- 一個簡單的jdbcRealm 來自:https://blog.csdn.net/qq_31307269/article/details/70237295 -->
    <bean id="sampleRealm"
        class="org.apache.shiro.realm.jdbc.JdbcRealm">
        <!-- dataSource數據源,可以引用spring中配置的數據源 -->
        <property name="dataSource" ref="dataSource" />
        <!-- authenticationQuery登錄認證用戶的查詢SQL,需要用登錄用戶名作為條件,查詢密碼字段。 -->
        <property name="authenticationQuery"
            value="select t.password from my_user t where t.username = ?" />
        <!-- userRolesQuery用戶角色查詢SQL,需要通過登錄用戶名去查詢。查詢角色字段 -->
        <property name="userRolesQuery"
            value="select a.rolename from my_user_role t left join my_role a on t.roleid = a.id where t.username = ? " />
            <!-- permissionsQuery用戶的權限資源查詢SQL,需要用單一角色查詢角色下的權限資源,如果存在多個角色,則是遍歷每個角色,分別查詢出權限資源並添加到集合中。 -->
        <property name="permissionsQuery"
            value="SELECT B.PERMISSION FROM MY_ROLE T LEFT JOIN MY_ROLE_PERMISSION A ON T.ID = A.ROLE_ID LEFT JOIN MY_PERMISSION B ON A.PERMISSION = B.ID WHERE T.ROLENAME = ? " />
        <!-- permissionsLookupEnabled默認false。False時不會使用permissionsQuery的SQL去查詢權限資源。設置為true才會去執行。 -->
        <property name="permissionsLookupEnabled" value="true" />
        <!-- saltStyle密碼是否加鹽,默認是NO_SALT不加鹽。加鹽有三種選擇CRYPT,COLUMN,EXTERNAL。這里按照不加鹽處理。 -->
        <property name="saltStyle" value="NO_SALT" />
        <!-- credentialsMatcher密碼匹配規則 -->
        <property name="credentialsMatcher" ref="credentialsMatcher" />
    </bean>

    <!-- securityManager安全管理器 -->
    <bean id="securityManager"
        class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="myRealm"></property>
    </bean>
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- id屬性值要對應 web.xml中shiro的filter對應的bean -->
    <bean id="shiroFilter"
        class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"></property>
        <!-- loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求地址將由formAuthenticationFilter進行表單認證 -->
        <property name="loginUrl" value="/login"></property>
        <!-- 認證成功統一跳轉到first.action,建議不配置,shiro認證成功會默認跳轉到上一個請求路徑 -->
        <!-- <property name="successUrl" value="/first.action"></property> -->
        <!-- 通過unauthorizedUrl指定沒有權限操作時跳轉頁面,這個位置會攔截不到,下面有給出解決方法 -->
        <!-- <property name="unauthorizedUrl" value="/refuse.jsp"></property> -->

        <!-- 過濾器定義,從上到下執行,一般將/**放在最下面 -->
        <property name="filterChainDefinitions">
            <!-- 
            過濾器簡稱        對應的java類
            anon            org.apache.shiro.web.filter.authc.AnonymousFilter
            authc           org.apache.shiro.web.filter.authc.FormAuthenticationFilter
            authcBasic      org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
            perms           org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
            port            org.apache.shiro.web.filter.authz.PortFilter
            rest            org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
            roles           org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
            ssl             org.apache.shiro.web.filter.authz.SslFilter
            user            org.apache.shiro.web.filter.authc.UserFilter
            logout          org.apache.shiro.web.filter.authc.LogoutFilter
            ————————————————
            版權聲明:本文為CSDN博主「a745233700」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
            原文鏈接:https://blog.csdn.net/a745233700/article/details/81350191
            -->
            <value>
                # 對靜態資源設置匿名訪問 
                /images/** = anon
                /js/** = anon
                /styles/** = anon
                /validatecode.jsp=anon
                /index=anon
                
                # 請求logout.action地址,shiro去清除session 
                /logout.action = logout
                
                # /**=anon 所有的url都可以匿名訪問,不能配置在最后一排,不然所有的請求都不會攔截 
                # /**=authc 所有的url都必須通過認證才可以訪問 
                /** = authc
            </value>
        </property>
    </bean>

    <!-- 解決shiro配置的沒有權限訪問時,unauthorizedUrl不跳轉到指定路徑的問題 -->
    <bean
        class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="org.apache.shiro.authz.UnauthorizedException">/refuse</prop>
            </props>
        </property>
    </bean>

</beans>
View Code

針對上邊配置內容需要注意以下幾點:

1)其實內部使用了了散列算法,對應散列算法使用示例:

package com.dx.test;

import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.junit.Test;

import com.dx.test.util.RandomUtil;

public class SaltTest {
    @Test
    public void testSalt() {
        String password = "1234";
        String salt = RandomUtil.getSalt(12);
        int hashIterations=8;

        System.out.println(salt);
        
        /**
         *  Md5Hash(Object source, Object salt, int hashIterations)
         *  source:明文,原始密碼
         *  salt:鹽,通過使用隨機數
         *  hashIterations:散列次數
         */
        Md5Hash md5Hash = new Md5Hash(password, salt, hashIterations);
        String md5Pwd = md5Hash.toString();
        System.out.println(md5Pwd);
     
        /**
         *  SimpleHash(String algorithmName, Object source, Object salt, int hashIterations)
         *  algorithmName:算法名稱
         *  source:明文,原始密碼
         *  salt:鹽
         *  hashIterations:散列次數
         */
        SimpleHash simpleHash = new SimpleHash("md5", password, salt, hashIterations);
        System.out.println(simpleHash);

    }
}

2)shiro內置filter,上邊提到了anon、logout、authc,實際上還有更多其他的內置filter:

Filter Name Class
anon org.apache.shiro.web.filter.authc.AnonymousFilter
authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
logout org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter
perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port org.apache.shiro.web.filter.authz.PortFilter
rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl org.apache.shiro.web.filter.authz.SslFilter
user org.apache.shiro.web.filter.authc.UserFilter


后邊會單獨抽出一章節來講述具體這些filter的原理,以及它們在shiro框架中都哪些地方會用到。

3)上邊寫了 /**=authc,實際上也可以對每個url指定一個具體權限,比如:

/article/edit=authc, roles[admin,user], perms[article:edit]

4)上邊securityManager下配置了一個realm,實際上也可以配置多個realm

<!--  認證策略 -->
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">  
    <property name="authenticationStrategy">  
        <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy" />  
    </property>  
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="authenticator" ref="authenticator" /> 
        <property name="realms" >
            <list>
                   <ref bean="myRealm"/>
                <bean id="myRealm2" class="com.my.test.Realm2" />
            </list>
        </property>
        <property name="sessionManager" ref="sessionManager"></property>
</bean>

當配置多個realms時,需要指定authenticator認證器的'authenticationStrategy(認證策略)屬性'。

5)securityManger除了可以設置上邊屬性外,還可以設置 sesssionManager、cacheManager、rememberMeManager 屬性。
sessionManager:對session進行配置管理;
cacheManager:對認證信息、授權信息進行緩存配置管理;
rememberMeManager:對rememberMe功能進行配置管理。

<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"/>

<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    <constructor-arg value="rememberMe"/>
    <property name="httpOnly" value="true"/>
    <property name="maxAge" value="2592000"/><!-- 30 days -->
</bean>

<!-- rememberMe管理器  -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
    <property name="cipherKey"
              value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>
    <property name="cookie" ref="rememberMeCookie"/>
</bean>

<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <!--sesssionDao可以自己擴展實現,可以繼承其他三方實現-->
    <property name="sessionDAO" ref="sessionDAO"/>
    <!-- session的失效時長,單位毫秒 1小時: 3600000, itzixi站點設置以 6小時 為主:21600000 -->
    <!-- 設置全局會話超時時間,默認30分鍾,即如果30分鍾內沒有訪問會話將過期 1800000 -->
    <property name="globalSessionTimeout" value="21600000"/>
    <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
    <!-- 刪除失效的session -->
    <property name="deleteInvalidSessions" value="true"/>
    <!-- 是否開啟會話驗證器,默認是開啟的 -->
    <property name="sessionValidationSchedulerEnabled" value="true"/>
</bean>

<!-- 會話ID生成器 -->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>

<!-- 會話Cookie模板,使用sid存儲sessionid -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    <constructor-arg value="sid"/>
    <property name="httpOnly" value="true"/>
    <property name="maxAge" value="180000"/>
</bean>

<!-- 
    會話DAO,更多場景中會自定義或者使用三個的SessionDao,比如:
    https://gitee.com/supperzh/zb-shiro/blob/master/src/main/java/com/nbclass/config/ShiroConfig.java#L122
    https://github.com/alexxiyang/shiro-redis/tree/master/src/main/java/org/crazycake/shiro
-->
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
    <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
    <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
</bean>

<!-- 會話驗證調度器 -->
<bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
    <property name="sessionValidationInterval" value="1800000"/>
    <property name="sessionManager" ref="sessionManager"/>
</bean>

<!-- 會話管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <property name="globalSessionTimeout" value="1800000"/>
    <property name="deleteInvalidSessions" value="true"/>
    <property name="sessionValidationSchedulerEnabled" value="true"/>
    <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
    <property name="sessionDAO" ref="sessionDAO"/>
    <property name="sessionIdCookieEnabled" value="true"/>
    <property name="sessionIdCookie" ref="sessionIdCookie"/>
</bean>

<!--  認證策略 -->
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">  
    <property name="authenticationStrategy">  
        <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy" />  
    </property>  
</bean>

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="authenticator" ref="authenticator" /> 
    <!-- <property name="realm" ref="shiroDbRealm"/> -->
    <property name="realms" >
        <list>
               <ref bean="myRealm"/>
            <bean id="myRealm2" class="com.my.test.Realm2" />
        </list>
    </property>
    <property name="sessionMode" value="native"/>
    <property name="cacheManager" ref="cacheManager"/>
    <property name="sessionManager" ref="sessionManager"/>
    <property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
View Code

自定義Realm

系統存儲結構設計

自定義的Realm會使用mysql中的數據作為認證、授權數據,對應表結構設計為:sys_user、sys_role、sys_permission、sys_role_permission、sys_user_role。

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL,
  `nick_name` varchar(64) DEFAULT NULL,
  `signature` varchar(512) DEFAULT NULL,
  `email` varchar(256) NOT NULL,
  `phone` varchar(16) DEFAULT NULL,
  `password` varchar(64) NOT NULL,
  `salt` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `status` int(4) NOT NULL DEFAULT '0',
  `expire_time` date NOT NULL DEFAULT '9999-12-01',
  `create_time` datetime NOT NULL,
  `create_user` varchar(64) DEFAULT NULL,
  `create_user_id` varchar(64) DEFAULT NULL,
  `modify_time` datetime NOT NULL,
  `modify_user` varchar(64) DEFAULT NULL,
  `modify_user_id` varchar(64) DEFAULT NULL,
  `version` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_username` (`username`),
  KEY `idx_username` (`username`),
  KEY `idx_email` (`email`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
BEGIN;
INSERT INTO `sys_user` VALUES (1, 'admin', 'administrator', '管理員', 'admin@test.com', '010-03838232', '3bb04e43e6f8b6775d3fb125b3aa02f6', 'gT1jO9zW5oY7', 0, '9999-12-01', '2019-12-23 15:31:38', NULL, NULL, '2019-12-23 15:31:46', NULL, NULL, 0);
COMMIT;

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(64) NOT NULL,
  `description` varchar(512) DEFAULT NULL,
  `create_time` date NOT NULL,
  `create_user` varchar(64) DEFAULT NULL,
  `create_user_id` varchar(64) DEFAULT NULL,
  `modify_time` date NOT NULL,
  `modify_user` varchar(64) DEFAULT NULL,
  `modify_user_id` varchar(64) DEFAULT NULL,
  `version` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_role_name` (`role_name`) USING BTREE,
  KEY `idx_role_name` (`role_name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
BEGIN;
INSERT INTO `sys_role` VALUES (1, 'admin', '管理員角色', '2019-12-23', NULL, NULL, '2019-12-23', NULL, NULL, 0);
COMMIT;

-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `permission_name` varchar(64) NOT NULL,
  `permission_value` varchar(64) NOT NULL,
  `description` varchar(512) DEFAULT NULL,
  `create_time` date NOT NULL,
  `create_user` varchar(64) DEFAULT NULL,
  `create_user_id` varchar(64) DEFAULT NULL,
  `modify_time` date NOT NULL,
  `modify_user` varchar(64) DEFAULT NULL,
  `modify_user_id` varchar(64) DEFAULT NULL,
  `version` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_permission_name` (`permission_name`),
  KEY `idx_permission_name` (`permission_name`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_permission
-- ----------------------------
BEGIN;
INSERT INTO `sys_permission` VALUES (1, 'queryArticle', 'article:query', NULL, '2019-12-23', NULL, NULL, '2019-12-23', NULL, NULL, 0);
INSERT INTO `sys_permission` VALUES (2, 'deleteArticle', 'article:delete', NULL, '2019-12-23', NULL, NULL, '2019-12-23', NULL, NULL, 0);
INSERT INTO `sys_permission` VALUES (3, 'updateArticle', 'article:update', NULL, '2019-12-23', NULL, NULL, '2019-12-23', NULL, NULL, 0);
COMMIT;


-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `role_id` int(20) NOT NULL,
  `permission_id` int(20) NOT NULL,
  `create_time` date NOT NULL,
  `create_user` varchar(64) DEFAULT NULL,
  `create_user_id` varchar(64) DEFAULT NULL,
  `modify_time` date NOT NULL,
  `modify_user` varchar(64) DEFAULT NULL,
  `modify_user_id` varchar(64) DEFAULT NULL,
  `version` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_role_id_permission_id` (`role_id`,`permission_id`),
  KEY `idx_role_id` (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
BEGIN;
INSERT INTO `sys_role_permission` VALUES (1, 1, 1, '2019-12-23', NULL, NULL, '2019-12-23', NULL, NULL, 0);
INSERT INTO `sys_role_permission` VALUES (2, 1, 2, '2019-12-23', NULL, NULL, '2019-12-23', NULL, NULL, 0);
INSERT INTO `sys_role_permission` VALUES (3, 1, 3, '2019-12-23', NULL, NULL, '2019-12-23', NULL, NULL, 0);
COMMIT;


-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `role_id` int(20) NOT NULL,
  `user_id` int(20) NOT NULL,
  `create_time` date NOT NULL,
  `create_user` varchar(64) DEFAULT NULL,
  `create_user_id` varchar(64) DEFAULT NULL,
  `modify_time` date NOT NULL,
  `modify_user` varchar(64) DEFAULT NULL,
  `modify_user_id` varchar(64) DEFAULT NULL,
  `version` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_role_id` (`role_id`),
  KEY `un_user_id_role_id` (`role_id`,`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
BEGIN;
INSERT INTO `sys_user_role` VALUES (1, 1, 1, '2019-12-23', NULL, NULL, '2019-12-23', NULL, NULL, 0);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
View Code

備注:

1)查看某個用戶是否有擁有的角色接口:

    @Options(useCache = true, flushCache = Options.FlushCachePolicy.FALSE, timeout = 60000)
    @ResultMap("sysRoleResult")
    @Select(value = { //
            "<script>", //
            "SELECT t10.* ", //
            "FROM `sys_role` as t10 ", //
            "INNER JOIN `sys_user_role` as t11 on t10.`id`=t11.`role_id` ", //
            "<where> ", //
            " <if test='userId!=null and !userId.equals(0)'>", //
            "     and t11.`user_id`=#{userId}", //
            " </if>", //
            "</where>", //
            "</script>" //
    })
    List<SysRole> getByUserId(@Param("userId") Long userId);

2)查看某個用戶擁有的角色下資源接口:

    @Options(useCache = true, flushCache = Options.FlushCachePolicy.FALSE, timeout = 60000)
    @ResultMap("sysPermissionResult")
    @Select(value = { //
            "<script>", //
            "SELECT t10.* ", //
            "FROM `sys_permission` as t10", //
            "INNER JOIN `sys_role_permission` as t11 on t10.`id`=t11.`permission_id` ", //
            "<where> ", //
            //"  <if test=\"roleIdList!=null && !roleIdList.isEmpty()\">",
            "     <foreach collection=\"roleIdList\" index=\"index\" item=\"item\" open=\"and t11.`role_id` in (\" separator=\",\" close=\")\">", //
            "           #{item} ", //
            "     </foreach>", //
            //"  </if>", //
            "</where>", //
            "</script>" //
    })
    List<SysPermission> getByRoleIds(@Param("roleIdList") List<Long> roleIdList);

自定義MyRealm.java

自定義MyRealm.java代碼如下:

public class MyRealm extends AuthorizingRealm {
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private SysRoleMapper sysRoleMapper;
    @Autowired
    private SysPermissionMapper sysPermissionMapper;

    /**
     * 設置realm的名稱
     */
    @Override
    public void setName(String name) {
        super.setName("myRealm");
    }

    /**
     * 認證使用(就是登錄)<br>
     * 所謂的認證就是 和配置文件shiro.ini、數據庫、內存中獲取到用戶的認證信息, 與用戶輸入的信息進行驗證對比:<br>
     * 如果驗證通過,就返回驗證結果;如果驗證失敗,就返回異常信息。<br>
     * <b>驗證對比過程一般如下:</b><br>
     * 1)一般情況下對比賬戶是否存在; <br>
     * 2)密碼是否正確; <br>
     * 3)是否鎖定; <br>
     * 4)是否賬戶過期;<br>
     * 等 <br>
     * 
     * @param token token是用戶輸入的:就是用戶點擊登錄,在后台調用subject.login(token)方法傳遞進來的token信息。
     */
    @SuppressWarnings({ "unused", "unlikely-arg-type" })
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("‘MyRealm’執行認證操作:");
        if (token == null) {
            throw new UnsupportedTokenException();
        }

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        if (userToken == null) {
            throw new UnsupportedTokenException();
        }

        // 獲取當前需要登錄的用戶
        String username = userToken.getUsername();
        String userPwd = String.valueOf(userToken.getPassword());
        if (StringUtils.isBlank(username) || userPwd == null) {
            throw new IncorrectCredentialsException("用戶名或密碼不正確");
        }

        SysUser sysUser = this.sysUserMapper.getByUsername(username);
        if (sysUser == null) {
            throw new UnknownAccountException("用戶名不存在");
        }

        Byte locked = Byte.valueOf("1");
        if (sysUser.getStatus().equals(locked)) {
            throw new LockedAccountException("用戶已鎖定");
        }
        Date now = new Date();
        if (sysUser.getExpireTime().before(now)) {
            throw new ExpiredCredentialsException("用戶過期");
        }

        // 從數據庫中取出密碼,密碼是加過鹽的
        String password = sysUser.getPassword();
        // 從數據庫中取出鹽
        ByteSource byteSource = ByteSource.Util.bytes(sysUser.getSalt());

        SysUser simpleSysUser=new SysUser();
        simpleSysUser.setUserName(sysUser.getUsername());
        simpleSysUser.setEmail(sysUser.getEmail());
        simpleSysUser.setPhone(sysUser.getPhone());
        simpleSysUser.setNickName(sysUser.getNickName());
        simpleSysUser.setSignature(sysUser.getSignature());
        
        // 該信息會提交給SecurityManager,在SecurityManager內部會進行驗證:
        // 用戶輸入的password+salt+md5+hashIterations 是否等於 db password? 等於則通過認證,否則不通過認證。
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(simpleSysUser, password.toCharArray(),
                byteSource, this.getName());
        return authenticationInfo;
    }

    /**
     * 授權使用(就是驗證用戶是否擁有某個角色、權限[資源])
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("‘MyRealm’執行授權操作:");
        SysUser simpleSysUser = (SysUser) principals.getPrimaryPrincipal();
        if (simpleSysUser == null) {
            throw new UnknownAccountException("用戶名不存在");
        }
        String username=simpleSysUser.getUsername();
        SysUser sysUser = this.sysUserMapper.getByUsername(username);
        if (sysUser == null) {
            throw new UnknownAccountException("用戶名不存在");
        }
        
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

        // 1)設置授權的‘角色’
        List<SysRole> sysRoleList = this.sysRoleMapper.getByUserId(sysUser.getId());
        Set<String> roleSet = new HashSet<String>();
        List<Long> roleIdList = new ArrayList<Long>();
        for (SysRole sysRole : sysRoleList) {
             // 如果角色名字變來變去不穩定,可以采用 id(但是id也確保不了不變,只能變動時修改權限驗證代碼了?不過一般權限不會太容易變動。)
            roleSet.add(sysRole.getRoleName());
            roleIdList.add(sysRole.getId());
        }
        simpleAuthorizationInfo.setRoles(roleSet);

        // 2) 設置授權的‘角色’下的‘資源’
        if (sysRoleList.size() > 0) {
            Set<String> permissionSet = new HashSet<String>();
            List<SysPermission> sysPermissionList = this.sysPermissionMapper.getByRoleIds(roleIdList);
            for (SysPermission sysPermission : sysPermissionList) {
                permissionSet.add(sysPermission.getPermissionValue());
            }
            simpleAuthorizationInfo.setStringPermissions(permissionSet);
        }

        return simpleAuthorizationInfo;
    }
}

說明:
1)該com.dx.test.shiro.MyRealm就是配置在applicationContext-shiro.xml中的myRealm bean。
2)Realm這里自定義采用繼承了 AuthorizingRealm,而AuthorizingRealm又繼承了 AuthenticatingRealm,因:
  AuthenticatingRealm 包含了認證抽象接口:protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
  AuthorizingRealm 包含了授權抽象接口:protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
  因此繼承了AuthorizingRealm的MyRealm,擁有了兩個功能:
  1)驗證用戶是否認證通過;
  2)驗證用戶是否已經授權某個角色、是否授權某個資源。

自定Realm系統如何實現認證、授權驗證?

登錄、登出

IndexController.java

@Controller
public class IndexController {
    /**
     * 訪問/index
     */
    @RequestMapping(value = "/index", method = RequestMethod.GET)
    public ModelAndView index() {
        ModelAndView maView = new ModelAndView();
        maView.setViewName("index.jsp");
        return maView;
    }

    /**
     * 訪問/login
     */
    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public ModelAndView login() {
        ModelAndView maView = new ModelAndView();
        maView.setViewName("login.jsp");
        return maView;
    }

    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public ModelAndView logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        
        ModelAndView maView = new ModelAndView();
        maView.setViewName("login.jsp");
        return maView;
    }

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String doLogin(String username, String password,Map<String,Object> map) {
        
        BaseResult baseResult = null;
        ResultEnum enu = null;

        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
            enu = ResultEnum.UserNameOrPasswordNull;
            baseResult = new BaseResult(enu.getCode(), enu.getMessage(), enu.getDesc());
            map.put("result", baseResult);
            return "login.jsp";
        }

        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password,true);
        try {
            subject.login(token);
        } catch (Exception e) {
            e.printStackTrace();
            enu = ResultEnum.LoginFail;
            baseResult = new BaseResult(enu.getCode(), enu.getMessage(), enu.getDesc());
            map.put("result", baseResult);
            return "login.jsp";
        }

        System.out.println("是否通過認證:" + subject.isAuthenticated());
        
        enu = ResultEnum.Success;
        baseResult = new BaseResult(enu.getCode(), enu.getMessage(), enu.getDesc());
        map.put("result", baseResult);
        return "redirect:admin/";
    }

}

登錄頁面/WEB-INF/views/login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <form action="login" method="POST">
        <!-- 用戶名 -->
        <label for="username">用戶:</label> <input id="username"
            name="username" type="text" /> <br>
        <!-- 用戶密碼 -->
        <label for="password">密碼:</label> <input id="password"
            name="password" type="password" /> <br>
        <!-- 登錄 -->
        <input type="submit" title="登錄"/>
    </form>
</body>
</html>

訪問 http://localhost:8080/mybaits-test-dynamic-sql/login 

輸入賬戶:admin,密碼:123456,點擊登錄,進入后台頁面/WEB-INF/views/admin.jsp

shiro權限驗證用法:

1)頁面標簽驗證:

/WEB-INF/views/admin.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Admin page</title>
</head>
<body>
    <!-- 原文鏈接:http://shiro.apache.org/web.html#Web-JSP%2FGSPTagLibrary -->
    <h3>The guest tag</h3>
    <shiro:guest>
    Hi there!  Please <a href="login">Login</a> or <a href="signup">Signup</a> today!<br>
    </shiro:guest>
    
    <h3>The user tag</h3>
    <shiro:user>
    Welcome back John!  Not John? Click <a href="login">here<a>
                to login.<br>
    </shiro:user>
    
    <h3>The authenticated tag</h3>
    <shiro:authenticated>
        <a href="updateAccount">Update your contact information</a>.<br>
    </shiro:authenticated>
    
    <h3>The notAuthenticated tag</h3>
    <shiro:notAuthenticated>
    Please <a href="login.jsp">login</a> in order to update your credit card information.<br>
    </shiro:notAuthenticated>
    
    <h3>The principal tag</h3>
    Hello,<shiro:principal />, how are you today?<br> 
    This is (mostly) equivalent to the following:<br> 
    Hello,<%=org.apache.shiro.SecurityUtils.getSubject().getPrincipal().toString()%>,how are you today?<br>
    
    <h4>Typed principal</h4>
    User Name:<!-- shiro:principal type="java.lang.String" --><br> 
    This is (mostly) equivalent to the following:<br> 
    User Name:<!-- %= org.apache.shiro.SecurityUtils.getSubject().getPrincipals().oneByType(String.class).toString() %--><br>
    
    <h4>Principal property</h4>
    Hello,<shiro:principal property="username" />, how are you today?<br> 
    This is (mostly) equivalent to the following:<br> 
    Hello,<%=((com.dx.test.model.SysUser) org.apache.shiro.SecurityUtils.getSubject().getPrincipal()).getUsername()%>,how are you today?<br> 
    Or, combined with the type attribute:<br>
    Hello,<shiro:principal type="com.dx.test.model.SysUser" property="username" />, how are you today?<br> 
    this is largely equivalent to the following:<br> 
    Hello,<%=org.apache.shiro.SecurityUtils.getSubject().getPrincipals().oneByType(com.dx.test.model.SysUser.class).getUsername().toString()%>,how are you today?<br>
    
    <h3>The hasRole tag</h3>
    <shiro:hasRole name="admin">
        <button class="btn btn-primary" id="btn_add_emp">新增</button>
        <br>
        <button class="btn btn-danger btn_all_del">刪除</button>
        <br>
    </shiro:hasRole>
    
    <h3>The lacksRole tag</h3>
    <shiro:lacksRole name="test">
    Sorry, you are not allowed to administer the system.<br>
    </shiro:lacksRole>
    
    <h3>The hasAnyRole tag</h3>
    <shiro:hasAnyRoles name="developer, project manager, admin">
    You are either a developer, project manager, or admin.<br>
    </shiro:hasAnyRoles>
    
    <h3>The hasPermission tag</h3>
    <shiro:hasPermission name="article:delete">
        <a href="deleteArticle.jsp">Delete a new article</a>
        <br>
    </shiro:hasPermission>
    
    <h3>The lacksPermission tag</h3>
    <shiro:lacksPermission name="artilce:view">
    Sorry, you are not allowed to view article.<br>
    </shiro:lacksPermission>

    <a href="deleteArticle">測試注解1</a>
    <a href="queryArticle">測試注解2</a>
</body>
</html>

頁面顯示效果:

2)使用注解方式驗證:

1)AdminController.java中頁面代碼使用注解進行權限驗證:

@Controller
@RequestMapping("/admin")
public class AdminController {
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String index() {
        return "admin.jsp";
    }

    @RequiresPermissions("article:delete")
    @RequestMapping(value = "/deleteArticle", method = RequestMethod.GET)
    public String deleteArticle() {
        return "article/list.jsp";
    }
    
    @RequiresPermissions("article:query")
    @RequestMapping(value = "/queryArticle", method = RequestMethod.GET)
    public String queryArticle() {
        return "article/list.jsp";
    }
    
}

2)查看頁面顯示效果驗證:shiro標簽 控制權限用法:

點擊“測試注解1”、“測試注解2”驗證使用注解方式驗證權限的用法(@RequiresPermissions("article:query"))。

 

本章中很多代碼都未在本文展示,具體可以參考源碼:https://github.com/478632418/springmv_without_web_xml/tree/master/mybaits-test-dynamic-sql

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM