本片文章源碼位置:《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>
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>
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>
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>
其他相關依賴:
<!--日志支持 --> <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>
另外搭建項目還需要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>
本章重點需要關注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>
注意:
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>
該配置文件主要配置了以下幾項內容:
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>
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>
針對上邊配置內容需要注意以下幾點:
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的原理,以及它們在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>
自定義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;
備注:
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
