本片文章源码位置:《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