SSM整合shiro进行权限控制以及shiro的一些特殊功能实现

项目结构图:


强力推荐《跟我学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


评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值