项目结构图:
强力推荐《跟我学shiro》!!
一、先新建一个maven项目,配置pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>Test</artifactId>
<packaging>war</packaging>
<version>0.0.1</version>
<name>Test</name>
<url>http://maven.apache.org</url>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!--1、 spring相关包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<!-- 要使用spring的aop,要么引入aspectj,要么cglib -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.11</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<!-- 事务相关spring包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.2.RELEASE</version>
</dependency>
<!--2、 数据库相关包 -->
<!-- 导入dbcp的jar包,用来在applicationContext.xml中配置数据库 -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<!-- 导入Mysql数据库链接jar包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- Maven中driverClassName为com.mysql.jdbc.Driver时不可使用6.0.3版本 -->
<version>5.1.18</version>
</dependency>
<!-- mybatis相关包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.2</version>
</dependency>
<!-- mybatis/spring包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!-- Ehcache实现,用于参考 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 3、日志文件管理包 -->
<!-- log start -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- 格式化对象,方便输出日志 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.1.41</version>
</dependency>
<!-- 简单日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.7</version>
</dependency>
<!-- log end -->
<!--4、 使用jackson处理对象与JSON之间相互转换 -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
<!-- 5、上传组件包 -->
<!-- 需要与commons-io配合使用 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!-- 开发IO流功能的工具类库 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<!--6、 用来处理常用的编码方法的工具类包 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
<!-- 7、支持servlet的jar包(HttpServletRequest、HttpServletResponse) -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<!--8、shiro相关包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- shiro整合util方法需要 -->
<dependency>
<groupId>com.google.collections</groupId>
<artifactId>google-collections</artifactId>
<version>1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<!-- 9、lombok 包-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
<!-- 10、编译jsp -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<!-- 11、JSTL标签类 -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.mail/mail -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.codehaus.jackson/jackson-core-asl -->
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.13</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.codehaus.jackson/jackson-mapper-asl -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.8.3</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>1.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>3.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version> 3.2.4.RELEASE </version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>Test</finalName>
<pluginManagement>
<plugins>
<!-- 逆向工程生成po类以及mapper -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.2</version>
<configuration>
<configurationFile>src/main/resources/generator.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
</plugin>
<!-- 运用maven指令进行test -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<!-- 默认加载本项目下的resource -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<!-- 运行maven指令运行tomcat -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
</plugin>
</plugins>
</pluginManagement>
<!-- 扫描的resource目录 -->
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
<!-- 根据不同环境配置 -->
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<!-- 数据库 -->
<ipPort>192.168.*.*:3306/dev_test</ipPort>
</properties>
<build>
<!-- 过滤的配置文件 -->
<filters>
<filter>src/main/resources/jdbc.properties</filter>
<filter>src/main/resources/log4j.properties</filter>
</filters>
</build>
</profile>
<profile>
<id>test</id>
<properties>
<!-- 数据库 -->
<ipPort>192.168.*.*:3306/creditloan_ph</ipPort>
</properties>
<build>
<filters>
<filter>src/main/resources/jdbc.properties</filter>
<filter>src/main/resources/log4j.properties</filter>
</filters>
</build>
</profile>
<profile>
<id>product</id>
<properties>
<!-- 数据库 -->
<ipPort>192.168.*.*:3306/creditloan_ph</ipPort>
</properties>
<build>
<filters>
<filter>src/main/resources/jdbc.properties</filter>
<filter>src/main/resources/log4j.properties</filter>
</filters>
</build>
</profile>
</profiles>
</project>
附:generator.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!-- 数据库驱动包位置 -->
<classPathEntry
location="E:\apache-maven-3.3.9\repo\mysql\mysql-connector-java\5.1.18\mysql-connector-java-5.1.18.jar" />
<context id="Tables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!-- 数据库链接URL、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://192.168.*.*:3306/dev_test" userId="用户名"
password="密码">
<!--<jdbcConnection driverClass="oracle.jdbc.driver.OracleDriver" connectionURL="jdbc:oracle:thin:@localhost:1521:orcl"
userId="msa" password="msa"> -->
</jdbcConnection>
<javaTypeResolver>
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer, 为 true时把JDBC DECIMAL和NUMERIC类型解析为java.math.BigDecimal -->
<property name="forceBigDecimals" value="true" />
</javaTypeResolver>
<!-- 生成实体类的包名和位置,这里配置将生成的实体类放在com.loan.entity这个包下 -->
<javaModelGenerator targetPackage="com.loan.entity"
targetProject=".\src\main\java\">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="true" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- 生成的SQL映射文件包名和位置,这里配置将生成的SQL映射文件放在com.loan.dao.xml这个包下 -->
<sqlMapGenerator targetPackage="com.loan.dao.xml"
targetProject=".\src\main\java\">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- 生成DAO的包名和位置,这里配置将生成的dao类放在com.loan.dao.mapper这个包下 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.loan.dao.mapper" targetProject=".\src\main\java\">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 要生成那些表(更改tableName和domainObjectName就可以) -->
<table tableName="sys_user_role" domainObjectName="UserRole"
enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false" />
</context>
</generatorConfiguration>
二、先把ssm的基本配置贴上来,就不详细赘述了:
1、application-context.xml(spring管理的相关配置)
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- 第一步:【1.整合dao】 将Mybatis和Spring进行整合MyBatis和Spring整合,通过Spring管理mapper接口。使用mapper的扫描器自动扫描mapper接口在Spring中进行注册。 -->
<!-- 需要配置:a、数据源 b、SqlSessionFactory c、mapper扫描器 -->
<!-- 1、数据源定义 -->
<!-- (1)加载jdbc.properties、redis.properties文件中的内容 -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
<!-- <value>classpath:redis.properties</value> -->
</list>
</property>
</bean>
<!-- (2)mysql数据源配置 -->
<!-- a、数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${connection.driverClassName}" />
<property name="url" value="${connection.url}" />
<property name="username" value="${connection.username}" />
<property name="password" value="${connection.password}" />
<property name="maxActive" value="${connection.maxActive}" />
<property name="maxIdle" value="${connection.maxIdle}" />
<property name="minIdle" value="${connection.minIdle}" />
<property name="removeAbandoned" value="${connection.removeAbandoned}" />
<property name="removeAbandonedTimeout" value="${connection.removeAbandonedTimeout}" />
<property name="logAbandoned" value="${connection.logAbandoned}" />
<property name="defaultAutoCommit" value="${connection.defaultAutoCommit}" />
<property name="defaultReadOnly" value="${connection.defaultReadOnly}" />
<property name="validationQuery" value="${connection.validationQuery}" />
<property name="testOnBorrow" value="${connection.testOnBorrow}" />
</bean>
<!-- b、sqlSessionFactory:创建sqlSessionFactory,同时指定数据源 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis-config.xml" />
<!-- 自动扫描mapper目录, 省掉mybatis-config.xml里的手工配置 -->
<property name="mapperLocations">
<list>
<value>classpath:com/loan/dao/xml/*.xml</value>
</list>
</property>
</bean>
<!-- c、mapper扫描器:通过扫描的模式,扫描目录在com/loan/mapper目录下 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.loan.dao.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
<!-- 第二步:通过Spring管理Service接口。使用配置方式将Service接口配置在Spring配置文件中。实现事务控制。 -->
<!-- (事务管理) -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 使用annotation定义数据库事务,这样可以在类或方法中直接使用@Transactional注解来声明事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="load*" propagation="SUPPORTS" read-only="true" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="search*" propagation="SUPPORTS" read-only="true" />
<tx:method name="approve" propagation="REQUIRED" />
<tx:method name="undo" propagation="REQUIRED" />
<tx:method name="*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceMethod"
expression="execution(* com.loan.service..*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" />
</aop:config>
<!-- spring管理:自动搜索注解路径 在xml配置了这个标签后,spring可以自动去扫描base-pack下面或者子包下面的Java文件,如果扫描到有@Component @Controller@Service等这些注解的类,则把这些类注册为bean-->
<context:component-scan base-package="com.loan"></context:component-scan>
</beans>
附:
a、jdbc.properties
connection.driverClassName=com.mysql.jdbc.Driver
connection.url=jdbc:mysql://192.168.*.*:3306/dev_test?useUnicode=true&characterEncoding=UTF-8
connection.username=user
connection.password=pwd
connection.initialSize=0
connection.maxActive=100
connection.maxIdle=30
connection.minIdle=5
connection.maxWait=5000
connection.removeAbandoned=true
connection.removeAbandonedTimeout=3000
connection.logAbandoned=false
connection.defaultAutoCommit=true
connection.defaultReadOnly=false
connection.validationQuery=SELECT 1
connection.testOnBorrow=true
b、mybatis-config.xml
<?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>
<!--整合Spring的时候 只有 settings typeAliases mapper 三个属性有用, 其余的要在spring总配置文件中会覆盖 -->
<settings>
<!-- 全局映射器,是否启用缓存 -->
<setting name="cacheEnabled" value="true" />
<!-- 查询时,关闭关联对象即时加载以提高性能 -->
<!-- 设置关联对象加载的形态,此处为按需加载字段(加载字段由SQL指 定),不会加载关联表的所有字段,以提高性能 -->
<setting name="aggressiveLazyLoading" value="false" />
<!-- 对于未知的SQL查询,允许返回不同的结果集以达到通用的效果 -->
<setting name="multipleResultSetsEnabled" value="true" />
<!-- 允许使用列标签代替列名 -->
<setting name="useColumnLabel" value="true" />
<!-- 允许使用自定义的主键值(比如由程序生成的UUID 32位编码作为键值),数据表的PK生成策略将被覆盖 -->
<setting name="useGeneratedKeys" value="true" />
<!-- 给予被嵌套的resultMap以字段-属性的映射支持 -->
<setting name="autoMappingBehavior" value="FULL" />
<!-- 对于批量更新操作缓存SQL以提高性能 -->
<!-- <setting name="defaultExecutorType" value="BATCH" /> -->
<!-- 数据库超过25000秒仍未响应则超时 -->
<setting name="defaultStatementTimeout" value="25000" />
</settings>
</configuration>
2、spring-mvc.xml(SpringMVC的相关配置)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.2.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">
<!-- 使用 mvc:annotation-driven代替注解映射器和注解适配器配置 mvc:annotation-driven默认加载很多的参数绑定方法,
比如json转换解析器就默认加载了,如果使用mvc:annotation-driven不用配置上边的RequestMappingHandlerMapping和RequestMappingHandlerAdapter
实际开发时使用mvc:annotation-driven -->
<mvc:annotation-driven />
<context:component-scan base-package="com.loan" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
4、log4j.properties
log4j.rootLogger=INFO, stdout, logfile
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.threshold=INFO
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d \u4FE1\u9500\u7F51\u7AD9\u540E\u53F0\u7BA1\u7406 -->%5p{%F:%L}-%m%n
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logfile.threshold=ERROR
log4j.appender.logfile.File=${catalina.home}/logs/Test/Test
log4j.appender.logfile.DatePattern='-'yyyy-MM-dd-HH-mm'.log'
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d \u4FE1\u9500\u7F51\u7AD9\ -->%5p{%F:%L}-%m%n
5、web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<context-param>
<param-name>webAppRootKey</param-name>
<param-value>test</param-value>
</context-param>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:log4j.properties</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<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>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:application-context.xml
classpath:spring-shiro.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>
至此,ssm基本配置完成
三、整合shiro
1、spring-shiro.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util" xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
default-lazy-init="false">
<!-- 缓存管理器使用Ehcache实现 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />
</bean>
<!-- 凭证匹配器 -->
<bean id="credentialsMatcher" class="com.loan.credentials.RetryLimitHashedCredentialsMatcher">
<constructor-arg ref="cacheManager" />
<property name="hashAlgorithmName" value="md5" />
<property name="hashIterations" value="2" />
<property name="storedCredentialsHexEncoded" value="true" />
</bean>
<!-- Realm实现 -->
<bean id="userRealm" class="com.loan.realm.UserRealm">
<!-- <property name="userService" ref="userService" /> -->
<property name="credentialsMatcher" ref="credentialsMatcher" />
<property name="cachingEnabled" value="true" />
<property name="authenticationCachingEnabled" value="true" />
<property name="authenticationCacheName" value="authenticationCache" />
<property name="authorizationCachingEnabled" value="true" />
<property name="authorizationCacheName" value="authorizationCache" />
</bean>
<!-- 会话ID 生成器 -->
<bean id="sessionIdGenerator"
class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator" />
<!-- 会话DAO -->
<bean id="sessionDAO"
class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<property name="activeSessionsCacheName" value="shiro-activeSessionCache" />
<property name="sessionIdGenerator" ref="sessionIdGenerator" />
</bean>
<!-- 会话验证调度器 sessionValidationInterval:设置调度时间间隔 -->
<bean id="sessionValidationScheduler"
class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
<property name="sessionValidationInterval" value="18000000" />
<property name="sessionManager" ref="sessionManager" />
</bean>
<!-- 会话Cookie 模板 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="sid" />
<property name="httpOnly" value="true" />
<property name="maxAge" value="1800" />
</bean>
<!-- 记住密码cookie -->
<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天 30*24*60*60 -->
</bean>
<!-- rememberMe管理器,cipherKey是加密rememberMe Cookie的密钥;默认AES算 -->
<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>
<!-- 会话管理器 globalSessionTimeout:设置全局会话超时时间,默认30分钟,即如果30分钟内没有访问会话将过期 sessionValidationSchedulerEnabled:是否开启会话验证器,默认是开启的 -->
<bean id="sessionManager"
class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="globalSessionTimeout" value="18000000" />
<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" />
<property name="sessionListeners" ref="sessionListener1" />
</bean>
<!-- 监听会话 -->
<bean id="sessionListener1" class="com.loan.util.MySessionListener1"></bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm" />
<property name="sessionManager" ref="sessionManager" />
<property name="cacheManager" ref="cacheManager" />
<property name="rememberMeManager" ref="rememberMeManager" />
</bean>
<!-- kickoutSessionControlFilter 用于控制并发登录人数的 -->
<bean id="kickoutSessionControlFilter"
class="com.loan.util.KickoutSessionControlFilter">
<property name="cacheManager" ref="cacheManager" />
<property name="sessionManager" ref="sessionManager" />
<property name="kickoutAfter" value="false" />
<property name="maxSession" value="2" />
<property name="kickoutUrl" value="/login?kickout=1" />
</bean>
<!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
<bean
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod"
value="org.apache.shiro.SecurityUtils.setSecurityManager" />
<property name="arguments" ref="securityManager" />
</bean>
<!-- Shiro 生命周期处理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean id="formAuthenticationFilter"
class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
<property name="usernameParam" value="username" />
<property name="passwordParam" value="password" />
<property name="rememberMeParam" value="rememberMe" />
<property name="loginUrl" value="/login" />
</bean>
<!-- Shiro 的Web过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login" />
<property name="successUrl" value="/index" />
<property name="unauthorizedUrl" value="/unauthorized" />
<property name="filters">
<util:map>
<entry key="authc" value-ref="formAuthenticationFilter" />
<entry key="kickout" value-ref="kickoutSessionControlFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/static/** = anon
/index = anon
/unauthorized = anon
/login =authc
/logout = logout
/admin/**=user,kickout
</value>
</property>
</bean>
<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.apache.shiro.authz.UnauthorizedException">
/unauthorized
</prop>
<prop key="org.apache.shiro.authz.UnauthenticatedException">
/login
</prop>
</props>
</property>
</bean>
</beans>
附:
a、ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="es">
<diskStore path="java.io.tmpdir"/>
<!-- 密码输入错误 锁定1小时 -->
<!-- timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒) -->
<!-- timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 -->
<cache name="passwordRetryCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<!-- 权限记录缓存 锁定1小时 -->
<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<!-- 登录认证记录缓存 锁定10分钟 -->
<cache name="authenticationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>
<!-- 会话次数缓存 -->
<cache name="shiro-activeSessionCache"
maxEntriesLocalHeap="10000"
overflowToDisk="false"
eternal="false"
diskPersistent="false"
timeToLiveSeconds="0"
timeToIdleSeconds="0"
statistics="true"/>
</ehcache>
b、RetryLimitHashedCredentialsMatcher.java
package com.loan.credentials;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Resource;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.SaltedAuthenticationInfo;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
/**
* <p>
* User: Zhang Kaitao
* <p>
* Date: 14-1-28
* <p>
* Version: 1.0
*/
public class RetryLimitHashedCredentialsMatcher extends
HashedCredentialsMatcher {
//AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减。
private Cache<String, AtomicInteger> passwordRetryCache;
public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
passwordRetryCache = cacheManager.getCache("passwordRetryCache");
}
//控制密码输入错误次数
@Override
public boolean doCredentialsMatch(AuthenticationToken token,
AuthenticationInfo info) {
String username = (String) token.getPrincipal();
// retry count + 1
AtomicInteger retryCount = passwordRetryCache.get(username);
System.out.println("retryCount:"+retryCount);
if (retryCount == null) {
retryCount = new AtomicInteger(0);
passwordRetryCache.put(username, retryCount);
}
if (retryCount.incrementAndGet() > 5) {
// if retry count > 5 throw
throw new ExcessiveAttemptsException();
}
boolean matches = super.doCredentialsMatch(token, info);
if (matches) {
// clear retry count
passwordRetryCache.remove(username);
}
return matches;
}
}
c、UserRealm.java
package com.loan.realm;
import org.apache.log4j.Logger;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import com.loan.entity.User;
import com.loan.pojo.UserParams;
import com.loan.service.ResourceService;
import com.loan.service.RoleService;
import com.loan.service.UserRoleService;
import com.loan.service.UserService;
/**
* @ClassName: UserRealm
* @Description: TODO(这里用一句话描述这个类的作用)
* @author jiayq
* @date 2016年9月5日 上午10:09:51
*
*/
public class UserRealm extends AuthorizingRealm {
private static final Logger logger = Logger.getLogger(UserRealm.class);
@javax.annotation.Resource
private UserRoleService userRoleService;
@javax.annotation.Resource
private ResourceService resourceService;
@javax.annotation.Resource
private UserService userService;
@javax.annotation.Resource
private RoleService roleService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
//直接调用getPrimaryPrincipal得到之前传入的用户名
User user = (User) principals.getPrimaryPrincipal();
logger.info("[用户:" + user.getUsername() + "|权限授权]");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//根据用户名调用UserService接口获取角色及权限信息
authorizationInfo.setRoles(roleService
.loadRoleIdByUsername(user.getUsername()));
authorizationInfo.setStringPermissions(resourceService
.loadPermissionsByUsername(user.getUsername()));
logger.info("[用户:" + user.getUsername() + "|权限授权完成]");
return authorizationInfo;
}
@Override
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
// 获取基于用户名和密码的令牌
// 实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = (String) token.getPrincipal();
System.out.println("pwd:"+token.getCredentials().toString());
logger.info("[用户:" + username + "|系统权限认证]");
User u = new User();
u.setUsername(username);
if (userService.find(new UserParams(u)).size() > 0) {
User sqluser = userService.find(new UserParams(u)).get(0);
// 交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
System.out.println("Realm:"+ByteSource.Util.bytes(sqluser.getCredentialsSalt()));
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(sqluser, sqluser.getPassword(),
ByteSource.Util.bytes(sqluser.getCredentialsSalt()), this.getName());// realm
logger.info("[用户:" + username + "|系统权限认证完成]");
return authenticationInfo;
}
return null;
}
}
d、MySessionListener1(用户测试会话过期时间)
package com.loan.util;
import java.util.Date;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
public class MySessionListener1 implements SessionListener {
@Override
public void onStart(Session session) {//会话创建时触发
System.out.println("会话创建:" + session.getId()+"》》时间:"+session.getLastAccessTime());
}
@Override
public void onExpiration(Session session) {//会话过期时触发
System.out.println("会话过期:" + session.getId()+"》》时间:"+new Date());
}
@Override
public void onStop(Session session) {//退出/会话过期时触发
System.out.println("会话停止:" + session.getId()+"》》时间:"+session.getLastAccessTime());
}
}
e、KickoutSessionControlFilter.java
package com.loan.util;
import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedList;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import com.loan.entity.User;
public class KickoutSessionControlFilter extends AccessControlFilter{
private String kickoutUrl; //踢出后到的地址
private boolean kickoutAfter; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户
private int maxSession; //同一个帐号最大会话数 默认1
private SessionManager sessionManager;
private Cache<String, Deque<Serializable>> cache;
public void setKickoutUrl(String kickoutUrl) {
this.kickoutUrl = kickoutUrl;
}
public void setKickoutAfter(boolean kickoutAfter) {
this.kickoutAfter = kickoutAfter;
}
public void setMaxSession(int maxSession) {
this.maxSession = maxSession;
}
public void setSessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}
public void setCacheManager(CacheManager cacheManager) {
this.cache = cacheManager.getCache("shiro-activeSessionCache");
}
/**
* 是否允许访问,返回true表示允许
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return false;
}
/**
* 表示访问拒绝时是否自己处理,如果返回true表示自己不处理且继续拦截器链执行,返回false表示自己已经处理了(比如重定向到另一个页面)。
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = getSubject(request, response);
if(!subject.isAuthenticated() && !subject.isRemembered()) {
//如果没有登录,直接进行之后的流程
return true;
}
Session session = subject.getSession();
String username = ((User)(subject.getPrincipal())).getUsername();
Serializable sessionId = session.getId();
// 初始化用户的队列放到缓存里
Deque<Serializable> deque = cache.get(username);
if(deque == null) {
deque = new LinkedList<Serializable>();
cache.put(username, deque);
}
//如果队列里没有此sessionId,且用户没有被踢出;放入队列
if(!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
deque.push(sessionId);
}
//如果队列里的sessionId数超出最大会话数,开始踢人
while(deque.size() > maxSession) {
Serializable kickoutSessionId = null;
if(kickoutAfter) { //如果踢出后者
kickoutSessionId=deque.getFirst();
kickoutSessionId = deque.removeFirst();
} else { //否则踢出前者
kickoutSessionId = deque.removeLast();
}
try {
Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
if(kickoutSession != null) {
//设置会话的kickout属性表示踢出了
kickoutSession.setAttribute("kickout", true);
}
} catch (Exception e) {//ignore exception
e.printStackTrace();
}
}
//如果被踢出了,直接退出,重定向到踢出后的地址
if (session.getAttribute("kickout") != null) {
//会话被踢出了
try {
subject.logout();
} catch (Exception e) {
}
WebUtils.issueRedirect(request, response, kickoutUrl);
return false;
}
return true;
}
}
2、spring-mvc.xml中加入shiro注解配置
<!-- 配置用于开启Shiro Spring AOP 权限注解的支持 -->
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" />
<aop:config proxy-target-class="true"></aop:config>
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<mvc:resources mapping="/static/**" location="/static/" cache-period="2592000"/>
3、web.xml中加入过滤器shiroFilter
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
四、基本配置已经完成,接下来通过简单的demo对逻辑进行梳理
1、登录验证逻辑
基本思路:新建用户(密码加密)->利用shiro的credentialsMatcher进行验证->完成登录(
重点:密码加密方式与credentialsMatcher的验证方式一致,当然你也可以自己实现验证)
(1)建立sys_user数据库表
(2)实体类User.java
package com.loan.entity;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
/**
* @ClassName: User
* @Description: TODO(这里用一句话描述这个类的作用)
* @author jiayq
* @date 2016年9月5日 下午2:31:58
*
*/
@SuppressWarnings("serial")
@Data
public class User implements Serializable {
private Long id;
private String username;// 用户名
private String workNo;// 工作编号
private String salt;// 盐(密码安全)
private String password;// 密码
private Integer age;// 年龄
private String state;// 状态
private Long orgId;
private String pic;
private String phone;
private String address;
private String email;
private Integer percent;
/**
* @Title: getCredentialsSalt
* @Description: salt = salt + username
* @param @return 设定文件
* @return String 返回类型
* @author jiayq
* @throws
*/
public String getCredentialsSalt() {
return username + salt;
}
}
(3)UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.loan.dao.mapper.UserMapper">
<!--增加-->
<insert id="save" parameterType="com.loan.entity.User" keyColumn="id" useGeneratedKeys="true" keyProperty="id">
insert into sys_user(id,username,work_no,salt,password,age,state,org_id,pic,phone,address,email,percent) values (#{id},#{username},#{workNo},#{salt},#{password},#{age},#{state},#{orgId},#{pic},#{phone},#{address},#{email},#{percent})
</insert>
</mapper>
(4)UserMapper.java
package com.loan.dao.mapper;
import java.util.List;
import com.loan.entity.User;
import com.loan.pojo.UserParams;
/**
* @ClassName: UserMapper
* @Description: TODO(这里用一句话描述这个类的作用)
* @author jiayq
* @date 2016年9月29日 下午4:08:43
*
*/
public interface UserMapper {
public void save(User user);
}
(5)UserService.java
package com.loan.service;
import java.util.List;
import com.loan.entity.User;
/**
* @ClassName: UserService
* @Description: TODO(这里用一句话描述这个类的作用)
* @author jiayq
* @date 2016年9月7日 上午10:09:32
*
*/
public interface UserService {
public void save(User user);
}
(6)UserServiceImpl.java
package com.loan.service.impl;
import java.util.List;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import com.loan.dao.mapper.UserMapper;
import com.loan.dao.mapper.UserRoleMapper;
import com.loan.entity.User;
import com.loan.pojo.UserParams;
import com.loan.service.UserService;
import com.loan.util.EndecryptUtils;
import com.loan.util.PasswordHelper;
/**
* @ClassName: UserServiceImpl
* @Description: TODO(这里用一句话描述这个类的作用)
* @author jiayq
* @date 2016年9月6日 上午9:24:49
*
*/
@Service("userService")
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Resource
private UserRoleMapper userRoleMapper;
@Override
public void save(User user) {
User u=new PasswordHelper().encryptPassword(user);
userMapper.save(u);
}
}
(7)PasswordHelper.java
package com.loan.util;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
import com.loan.entity.User;
public class PasswordHelper {
private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
private String algorithmName = "md5";
private final int hashIterations = 2;
public User encryptPassword(User user) {
if(user.getSalt()==null||("").equals(user.getSalt())){
user.setSalt(randomNumberGenerator.nextBytes().toHex());
}
String newPassword = new SimpleHash(algorithmName, user.getPassword(),
ByteSource.Util.bytes(user.getCredentialsSalt()), hashIterations).toHex();
user.setPassword(newPassword);
return user;
}
}
重点:
(8)新建用户
package com.loan.controller;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.stereotype.Controller;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.loan.entity.User;
import com.loan.service.UserService;
@Controller
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/application-context.xml")
@RequestMapping("/test")
public class TestController {
@Resource
private UserService userService;
@Test
public void userCreate(){
User user=new User();
user.setAge(20);
user.setPassword("123");
user.setUsername("shiroUser");
userService.save(user);
}
}
右键run as JUnit test
(9)简单login.jsp页面进行登录
<%@ page contentType="text/html;charset=UTF-8" language="java"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>登录</title>
<!-- Javascript -->
<script src="${pageContext.request.contextPath}/static/jquery-1.7.2.js"></script>
<script type="text/javascript">
//登录
function login() {
if ($("#userName").val() == null || $("#userName").val() == '') {
alert("请输入用户名!");
} else if ($("#pwd").val() == null || $("#pwd").val() == "") {
alert("密码不能为空!");
} else {
document.getElementById("form").submit();
}
}
//keydowm登录
function keydown_login() {
document.onkeydown = function (e){
var theEvent = window.event || e;
var code = theEvent.keyCode || theEvent.which;
if (code == 13 && (null != $("#pwd").val())) {
theEvent.returnValue = false;
theEvent.cancel = true;
document.getElementById("form").submit();
}
}
}
</script>
</head>
<body>
<form method="post" id="form" action="${pageContext.request.contextPath}/loginTest">
<div class="form-group">
<label>Username</label>
<input type="text" name="username" id="userName" />
<label style="color: red; font-size: 14px; float: right; margin-right: 35px;">${message}</label>
</div>
<div class="form-group">
<label >Password</label>
<input type="password" name="password" id="pwd" />
</div>
<div class="checkbox">
<label><input type="checkbox" name="rememberMe"/> RemeberMe</label>
</div>
<button type="button" class="btn" οnclick="login()">Sign in!</button>
</form>
</body>
</html>
(10)LoginController.java
package com.loan.controller;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.loan.entity.User;
import com.loan.service.UserService;
/**
* @ClassName: LoginController
* @Description: TODO(登录controller)
* @author jiayq
* @date 2016年9月5日 下午5:06:33
*
*/
@Controller
@RequestMapping("/")
public class LoginController {
@Resource
private UserService userService;
/**
* @Title: loginView
* @Description: TODO(转向登录界面)
* @param @return 设定文件
* @return String 返回类型
* @throws
*/
@RequestMapping(value = "/login")
public String loginView(){
return "login";
}
/**
* @Title: login1
* @Description: TODO(shiro+EndecryptUtils进行认证)
* @param @param request
* @param @param model
* @param @param username
* @param @param password
* @param @return 设定文件
* @return String 返回类型
* @throws
*/
@RequestMapping(value = "/loginTest")
public String login(HttpServletRequest request, Model model, String username, String password,boolean rememberMe) {
System.out.println("rememberMe:"+rememberMe);
//将form中的用户名密码传入Realm 的doGetAuthenticationInfo
UsernamePasswordToken token = new UsernamePasswordToken(username, password.toCharArray());
token.setRememberMe(rememberMe);
Subject currentUser = SecurityUtils.getSubject();
String error = "";
try {
currentUser.login(token);
} catch (UnknownAccountException ex) {// 用户名没有找到
error = "您输入的用户名不存在!";
} catch (IncorrectCredentialsException ex) {// 用户名密码不匹配
error = "用户名密码不匹配 !";
}
catch(ExcessiveAttemptsException e){
error="密码错误次数已超五次,账号锁定1小时!";
}
catch (AuthenticationException ex) {// 其他的登录错误
ex.printStackTrace();
error = "其他的登录错误 !";
}
// 验证是否成功登录的方法
if (currentUser.isAuthenticated()) {
return "redirect:/admin/index";
} else {
model.addAttribute("message", error);
currentUser.logout();
return "login";
}
}
}
结果:
重点逻辑:
五、密码输入错误账号锁定功能
前面给的代码中已经实现,主要梳理下逻辑:
六、并发登录人数控制
代码上头已经给出,主要梳理流程:
七、记住密码
spring-shiro.xml中相关配置:
jsp:
LoginController.java
八、shiro的权限授权
1、建立用户、角色、资源以及之间的相关表
(1)sys_user:
User.java
package com.loan.entity;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
/**
* @ClassName: User
* @Description: TODO(这里用一句话描述这个类的作用)
* @author jiayq
* @date 2016年9月5日 下午2:31:58
*
*/
@SuppressWarnings("serial")
@Data
public class User implements Serializable {
private Long id;
private String username;// 用户名
private String workNo;// 工作编号
private String salt;// 盐(密码安全)
private String password;// 密码
private Integer age;// 年龄
private String state;// 状态
private Long orgId;
private String pic;
private String phone;
private String address;
private String email;
private Integer percent;
/**
* @Title: getCredentialsSalt
* @Description: salt = salt + username
* @param @return 设定文件
* @return String 返回类型
* @author jiayq
* @throws
*/
public String getCredentialsSalt() {
return username + salt;
}
}
(2)sys_user_role:
UserRole.java
package com.loan.entity;
import java.io.Serializable;
import lombok.Data;
/**
* @ClassName: UserRole
* @Description: TODO(这里用一句话描述这个类的作用)
* @author jiayq
* @date 2016年9月20日 上午10:17:50
*
*/
@SuppressWarnings("serial")
@Data
public class UserRole implements Serializable {
private Long id;
private Long roleId;//角色id
private Long userId;//用户id
}
(3)sys_role:
Role.java
package com.loan.entity;
import java.io.Serializable;
import lombok.Data;
/**
* @ClassName: Role
* @Description: TODO(这里用一句话描述这个类的作用)
* @author jiayq
* @date 2016年9月20日 上午10:15:29
*
*/
@SuppressWarnings("serial")
@Data
public class Role implements Serializable{
private Long id;
private String name;//名称
private String description;//描述
private String state;//状态
private String code;//编码
private Long pid ;//父id
private String remark;//备注
//关联资源列表
private String resources;
}
(4)sys_resource:
Resource.java
package com.loan.entity;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
/**
* @ClassName: Resource
* @Description: TODO(这里用一句话描述这个类的作用)
* @author jiayq
* @date 2016年9月19日 下午4:57:11
*
*/
@SuppressWarnings("serial")
@Data
public class Resource implements Serializable{
private Long id;
private String name;//名称
//private ResourceType type = ResourceType.menu;//资源类型
private String type;
private Integer leaf;//0表示是叶子节点
private Long priority;//顺序
private Long pid;//父id
private String permission;//权限
private String status;//状态
private String url;//路径
private String outUrl;//站外路径
private String pic;
//关联的角色列表
private List<Role> roles;
private List<Resource> children = new ArrayList<Resource>();
}
(5)sys_role_resource:
RoleResource.java
package com.loan.entity;
import java.io.Serializable;
import lombok.Data;
/**
* @ClassName: RoleResource
* @Description: TODO(这里用一句话描述这个类的作用)
* @author jiayq
* @date 2016年9月20日 上午10:16:57
*
*/
@Data
public class RoleResource implements Serializable{
private Long id;
private Long roleId;//角色id
private Long resourceId;//资源id
}
2、新建角色Role为角色分配资源Resource(权限)
(1)新建资源:
@Test
public void resourceCreate(){
//建立三个资源
for(int i=1;i<4;i++){
com.loan.entity.Resource resource=new com.loan.entity.Resource();
resource.setName("resourceTest_"+i);
resource.setPermission("test:permission_"+i);
resourceService.save(resource);
}
}
(2)新建role
@Test
public void roleCreate(){
Role role=new Role();
role.setName("testRole");
roleService.save(role);
for(int i=143;i<146;i++){
RoleResource roleResource=new RoleResource();
roleResource.setRoleId(role.getId());
roleResource.setResourceId((long)i);
roleResourceService.save(roleResource);
}
}
(3)新建user
@Test
public void UserCreate(){
User user=new User();
user.setAge(20);
user.setPassword("123");
user.setUsername("shiroUser1");
userService.save(user);
UserRole ur=new UserRole();
ur.setRoleId((long)25);
ur.setUserId(user.getId());
userRoleService.save(ur);
}
至此,所以关系逻辑建立。
3、UserRealm权限授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals) {
//直接调用getPrimaryPrincipal得到之前传入的用户名
User user = (User) principals.getPrimaryPrincipal();
logger.info("[用户:" + user.getUsername() + "|权限授权]");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//根据用户名调用UserService接口获取角色及权限信息
authorizationInfo.setRoles(roleService
.loadRoleIdByUsername(user.getUsername()));
authorizationInfo.setStringPermissions(resourceService
.loadPermissionsByUsername(user.getUsername()));
logger.info("[用户:" + user.getUsername() + "|权限授权完成]");
return authorizationInfo;
}
roleMapper.xml
<select id="loadRoleIdByUsername" parameterType="java.lang.String" resultMap="Role_resultMap">
select sr.* from sys_role sr,sys_user su,sys_user_role sur WHERE su.username=#{username} and su.id=sur.user_id and sr.id=sur.role_id
</select>
resourceMapper.xml
<select id="loadPermissionByUsername" parameterType="java.lang.String" resultMap="Resource_resultMap">
SELECT sres.* from sys_user su,sys_user_role sur,sys_role sr,sys_role_resource srr,sys_resource sres WHERE su.username=#{name} and su.id=sur.user_id and sr.id=sur.role_id AND srr.role_id=sr.id and srr.resource_id=sres.id
</select>
3、通过jsp/注释测试
(1)jsp
<%@ page language="java" contentType="text/html; charset=utf-8"
pageEncoding="utf-8"%>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>test</title>
<script src="${pageContext.request.contextPath}/static/jquery-1.7.2.js"></script>
<script type="text/javascript">
function logout(){
$.post('${pageContext.request.contextPath}/logout',function(){
location.reload();
});
}
</script>
</head>
<body>
<shiro:guest>
欢迎游客访问,<a href="${pageContext.request.contextPath}/login">登录</a>
</shiro:guest>
<shiro:user>
欢迎<shiro:principal property="username"/>登录,<a href="javascript:logout();">退出</a>
</shiro:user>
<br/>
<shiro:authenticated>
用户[<shiro:principal property="username"/>]已身份验证通过
</shiro:authenticated>
<br/>
<shiro:hasRole name="testRole">
用户[<shiro:principal property="username"/>]拥有testRole角色<br/>
</shiro:hasRole>
<br/>
<shiro:hasPermission name="test:permission_1">
用户[<shiro:principal property="username"/>]拥有权限test:permission_1<br/>
</shiro:hasPermission>
</body>
</html>
登录结果:
(2)通过注解控制方法的访问:
@RequiresPermissions("test:permission_1")
@RequestMapping("/testAnnotation")
public String testAnnotation(){
return "/index";
}
@RequiresPermissions("test:nopermission")
@RequestMapping("/testAnnotation1")
public String testAnnotation1(){
return "index";
}
测试结果:
终于完了!!!
源代码下载:http://download.csdn.net/download/dreamer_8399/9959021