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

  • 相关阅读:
    * 结束Activity
    进度条ProgressBar
    StackView实现叠加在一起的图片循环移动像循环队列一样
    AdapterViewFlipper功能 自动播放的图片库
    Spinner功能和用法
    SimpleAdapter 网络视图:带预览的图片浏览器
    AutoCompleteTextView
    Faster R-CNN
    Fast R-CNN
    100个大型机器学习数据集汇总(CV/NLP/音频方向)
  • 原文地址:https://www.cnblogs.com/yy3b2007com/p/12098939.html
Copyright © 2020-2023  润新知