第1章 分页回顾
1.1 Oracle中分页的实现
1.1.1 Oracle中不带排序的分页
--每页显示5条记录,显示第二页
select * from (
select rownum rn, p.* from product p where rownum<=10
) t where t.rn>=6
1.1.2 Oracle中带排序的分页
select * from (
select rownum rn,t.* from (
select p.* from product p order by p.PRODUCTPRICE desc
) t where rownum<=10
) t2 where t2.rn>=6
1.1.3 分析
从上面2个分页语句来看,我们每次只需要传入两个参数,一个是开始的行号,一个是结束的行号即可实现分页查询。我们看看下面的分页图片,前台每次传过来的参数有每页显示5(size)条数据和当前页是第几页(page),根据这2参数,我们可以获得between后面2个参数的计算公式:
between 6 and 10
每页显示条数:size 5
当前页:page 2
6=(page-1)*size+1;
10=page*size;
1.2 实现产品列表分页查询
1.2.1 分页工具类的封装
通过上面的图片,可以看到,每次都有对应的分页信息需要现实,我们可以将对应的分页信息封装起来,在ssm-utils工程下创建PageBean类,封装代码如下:
package com.viking.util;
import java.util.List;
public class PageBean<T> {
// 当前页,手动添加的默认值是1
private int pageCode = 1;
// 总记录数
private int totalCount;
// 每页显示的记录条数,每页显示5条
private int pageSize = 5;
// 每页显示的数据
private List<T> beanList;
public int getPageCode() {
return pageCode;
}
public void setPageCode(int pageCode) {
this.pageCode = pageCode;
}
/**
* 调用getTotalPage() 获取到总页数
* JavaBean的属性规定:totalPage是JavaBean是属性 ${pageBean.totalPage}
* @return
*/
public int getTotalPage() {
// 计算
int totalPage = totalCount / pageSize;
// 说明整除
if(totalCount % pageSize == 0){
return totalPage;
}else{
return totalPage + 1;
}
}
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public List<T> getBeanList() {
return beanList;
}
public void setBeanList(List<T> beanList) {
this.beanList = beanList;
}
}
1.2.2 Controller代码实现
根据上面的分析,前台需要向后台传递2个参数,分别是每页显示多少条(size)和当前页(page),一般我们在查询列表页的时候,第一次请求不会加分页参数,所以应该要给他们默认参数,首次请求列表页,我们可以让size=5,page=1,这也符合工作中的现实要求。
修改ProductController,将列表查询的方法做如下改造:
/**
* 产品分页查询
* @return
*/
@RequestMapping("/list")
public String list(@RequestParam(value="page",defaultValue = "1",required = false) int page,
@RequestParam(value="size",defaultValue = "5",required = false) int size,
Model model){
// 调用业务分页查询
PageBean<Product> pageInfo = productService.pageList(page,size);
// 放model传给jsp
model.addAttribute("pageInfo",pageInfo);
return "product-list";
}
1.2.3 Service代码实现
修改ProductService接口,添加分页查询的方法
/**
* 产品分页查询
* @param page 页码
* @param size 每页显示记录数
* @return
*/
PageBean<Product> pageList(int page, int size);
修改ProductServiceImpl实现类
/**
* 产品分页查询
* @param page
* @param size
* @return
*/
@Override
public PageBean<Product> pageList(int page, int size) {
// 创建返回的分页信息对象
PageBean<Product> pageInfo = new PageBean<Product>();
// 设置每页显示的记录数
pageInfo.setPageSize(size);
// 设置当前页码
pageInfo.setPageCode(page);
// 获取总数
int total = productDao.getCount();
// 设置总数
pageInfo.setTotalCount(total);
// 计算开始行
int start = (page - 1) * size + 1;
// 结束的行号
int end = page * size;
// 分页查询结果
List<Product> products = productDao.pageList(start, end);
// 设置分页结果集
pageInfo.setBeanList(products);
return pageInfo;
}
1.2.4 Dao代码实现
修改ProductDao,由于我们之前传递的入参是一个JavaBean对象,所以可以直接用#{属性名}来取对应SQL语句占位符的参数,但是如果入参是多个基本数据类型或者多个String类型,我们就无法像之前那么去取,我们可以使用一个注解来实现参数别名的标记@Param(value=“别名”),用@Param之后,SQL语句就可以直接取@Param(value=“name”)中的name了。
/**
* 分页查询
* 我们这里采用了不使用排序的分页方式
* @return
* @param start
* @param end
*/
@Select("select t.* from (select rownum rn,p.* from product p where rownum<=#{end}) t where t.rn>=#{start}")
List<Product> pageList(@Param("start") int start, @Param("end") int end);
/**
* 获取总数
* @return
*/
@Select("select count(1) from product")
int getCount();
1.2.5 product-list.jsp改造
将c:forEach中的items所取的迭代对象换成pageInfo.beanList,其他不变。
<tbody>
<c:forEach items="${pageInfo.beanList}" var="product">
<tr>
<td><input name="ids" value="${product.id}" type="checkbox"></td>
//...略
</tr>
</c:forEach>
</tbody>
分页数据填充
<div class="pull-left">
<div class="form-group form-inline">
当前第${pageInfo.pageCode}页/共${pageInfo.totalPage}页,共${pageInfo.totalCount}条数据。 每页 <select class="form-control" onchange="submitPageSize(this)">
<option>5</option>
<option>10</option>
<option>15</option>
<option>20</option>
<option>50</option>
<option>80</option>
</select> 条
</div>
</div>
<script>
//每页显示条数变化
function submitPageSize(option) {
/**
* 提交查询
* option.value:获取select的值
*/
location.href='/product/list?size='+option.value;
}
</script>
<div class="box-tools pull-right">
<ul class="pagination">
<li><a href="/product/list?page=1&size=${pageInfo.pageSize}" aria-label="Previous">首页</a></li>
<li><a href="/product/list?page=${pageInfo.pageCode-1}&size=${pageInfo.pageSize}">上一页</a></li>
<c:forEach var="i" begin="1" end="${pageInfo.totalPage}">
<li><a href="/product/list?page=${i}&size=${pageInfo.pageSize}">${i}</a></li>
</c:forEach>
<li><a href="/product/list?page=${pageInfo.pageCode+1}&size=${pageInfo.pageSize}">下一页</a></li>
<li><a href="/product/list?page=${pageInfo.totalPage}&size=${pageInfo.pageSize}" aria-label="Next">尾页</a></li>
</ul>
</div>
第2章 分页插件PageHelper
2.1 PageHelper的介绍
PageHelper是国内非常优秀的一款开源的mybatis分页插件,它支持基本主流与常用的数据库,例如mysql、oracle、mariaDB、DB2、SQLite、Hsqldb等。
网址:https://pagehelper.github.io/
本项目在 github 的项目地址:https://github.com/pagehelper/Mybatis-PageHelper
本项目在 gitosc 的项目地址:http://git.oschina.net/free/Mybatis_PageHelper
分页插件学习文档:https://pagehelper.github.io/docs/howtouse/
2.2 PageHelper集成到项目中
引入分页插件有下面2种方式,推荐使用 Maven 方式。
2.2.1 引入jar包方式
可以从下面的地址中下载最新版本的 jar 包
https://oss.sonatype.org/content/repositories/releases/com/github/pagehelper/pagehelper/
http://repo1.maven.org/maven2/com/github/pagehelper/pagehelper/
由于使用了sql 解析工具,你还需要下载 jsqlparser.jar:
http://repo1.maven.org/maven2/com/github/jsqlparser/jsqlparser/0.9.5/
2.2.2 Maven方式
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.6</version>
</dependency>
2.2.4 配置PageHelper
特别注意,新版拦截器是 com.github.pagehelper.PageInterceptor。 com.github.pagehelper.PageHelper 现在是一个特殊的 dialect 实现类,是分页插件的默认实现类,提供了和以前相同的用法。
2.2.4.1 在 MyBatis 配置 xml 中配置拦截器插件
<!--
plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
properties?, settings?,
typeAliases?, typeHandlers?,
objectFactory?,objectWrapperFactory?,
plugins?,
environments?, databaseIdProvider?, mappers?
-->
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
<property name="param1" value="value1"/>
</plugin>
</plugins>
2.2.4.2 在 Spring 配置文件中配置拦截器插件
使用 spring 的属性配置方式,可以使用 plugins 属性像下面这样配置:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注意其他配置 -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<!--使用下面的方式配置参数,一行配置一个 -->
<value>
params=value1
</value>
</property>
</bean>
</array>
</property>
</bean>
2.2.4.3 分页插件参数介绍
- helperDialect:分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。 你可以配置helperDialect属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值:
oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby
特别注意:使用 SqlServer2012 数据库时,需要手动指定为 sqlserver2012,否则会使用 SqlServer2005 的方式进行分页。
你也可以实现 AbstractHelperDialect,然后配置该属性为实现类的全限定名称即可使用自定义的实现方法。
-
offsetAsPageNum:默认值为 false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为 true 时,会将 RowBounds 中的 offset 参数当成 pageNum 使用,可以用页码和页面大小两个参数进行分页。
-
rowBoundsWithCount:默认值为false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为true时,使用 RowBounds 分页会进行 count 查询。
-
pageSizeZero:默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果(相当于没有执行分页查询,但是返回结果仍然是 Page 类型)。
-
reasonable:分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
-
params:为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值, 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。
-
supportMethodsArguments:支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。 使用方法可以参考测试代码中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTest 和 ArgumentsObjTest。
-
autoRuntimeDialect:默认值为 false。设置为 true 时,允许在运行时根据多数据源自动识别对应方言的分页 (不支持自动选择sqlserver2012,只能使用sqlserver),用法和注意事项参考下面的场景五。
-
closeConn:默认值为 true。当使用运行时动态数据源或没有设置 helperDialect 属性自动获取数据库类型时,会自动获取一个数据库连接, 通过该属性来设置是否关闭获取的这个连接,默认true关闭,设置为 false 后,不会关闭获取的连接,这个参数的设置要根据自己选择的数据源来决定。
2.2.4.4 基本使用方法
PageHelper的基本使用有6种,大家可以查看文档,最常用的有两种
2.2.4.4.1 RowBounds方式的调用(了解)
List<Country> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(1, 10));
使用这种调用方式时,你可以使用RowBounds参数进行分页,这种方式侵入性最小,我们可以看到,通过RowBounds方式调用只是使用了这个参数,并没有增加其他任何内容。
分页插件检测到使用了RowBounds参数时,就会对该查询进行物理分页。
关于这种方式的调用,有两个特殊的参数是针对 RowBounds 的,你可以参看上面的分页插件参数介绍
注:不只有命名空间方式可以用RowBounds,使用接口的时候也可以增加RowBounds参数,例如:
//这种情况下也会进行物理分页查询
List<Country> selectAll(RowBounds rowBounds);
注意: 由于默认情况下的 RowBounds 无法获取查询总数,分页插件提供了一个继承自 RowBounds 的 PageRowBounds,这个对象中增加了 total 属性,执行分页查询后,可以从该属性得到查询总数。
2.2.4.4.2 PageHelper.startPage 静态方法调用(重点)
这种方式是我们要掌握的
在你需要进行分页的 MyBatis 查询方法前调用PageHelper.startPage 静态方法即可,紧跟在这个方法后的第一个MyBatis 查询方法会被进行分页。
//获取第1页,10条内容,默认查询总数count
PageHelper.startPage(1, 10);
//紧跟着的第一个select方法会被分页,查询所有
List<Country> list = countryMapper.selectIf();
2.2.4.4.3 PageInfo介绍
以下是PageHelper自带的分页对象,基本能满足我们平时工作的分页需求,等会我们就直接使用它。
public class PageInfo<T> implements Serializable {
private static final long serialVersionUID = 1L;
//当前页
private int pageNum;
//每页的数量
private int pageSize;
//当前页的数量
private int size;
//由于startRow和endRow不常用,这里说个具体的用法
//可以在页面中"显示startRow到endRow 共size条数据"
//当前页面第一个元素在数据库中的行号
private int startRow;
//当前页面最后一个元素在数据库中的行号
private int endRow;
//总记录数
private long total;
//总页数
private int pages;
//结果集
private List<T> list;
//前一页
private int prePage;
//下一页
private int nextPage;
//是否为第一页
private boolean isFirstPage = false;
//是否为最后一页
private boolean isLastPage = false;
//是否有前一页
private boolean hasPreviousPage = false;
//是否有下一页
private boolean hasNextPage = false;
//导航页码数
private int navigatePages;
//所有导航页号
private int[] navigatepageNums;
//导航条上的第一页
private int navigateFirstPage;
//导航条上的最后一页
private int navigateLastPage;
//...略
}
2.2.5 集成PageHelper
2.2.5.1 工程中导入PageHelper依赖
今天我们使用5.1.2的版本,分别在父工程和dao工程中导入以上包,父工程做版本的管理,子工程直接使用。
修改ssm-parent的pom.xml,在properties中加入版本
<properties>
//....略
//分页版本
<pagehelper.version>5.1.6</pagehelper.version>
</properties>
修改ssm-parent的pom.xml,在dependencyManagement的dependencies中加入如下依赖
</dependencyManagement>
<dependencies>
//...略
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
修改ssm-dao的pom.xml,在dependencies中加入如下依赖
<dependencies>
//...略
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
</dependency>
</dependencies>
2.2.5.2 项目中的配置
在ssm-dao工程中,修改spring-mybatis.xml,找到SqlSessionFactoryBean结点的配置,修改如下:
<!--SqlSessionFactoryBean-->
<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--注入数据源-->
<property name="dataSource" ref="dataSource" />
<!--集成分页插件-->
<property name="plugins">
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<!--数据库方言选中oracle-->
<prop key="helperDialect">oracle</prop>
<!--当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页-->
<prop key="reasonable">true</prop>
</props>
</property>
</bean>
</property>
</bean>
2.2.5.3 修改Controller
这回我们修改OrdersController类,将返回类型修改成PageInfot。
/***
* 订单列表查询
* @param model
* @return
*/
@RequestMapping("/list")
public String list(@RequestParam(value="page",defaultValue = "1",required = false) int page,
@RequestParam(value="size",defaultValue = "5",required = false) int size,
Model model){
// 调用业务查询所有的订单,使用pageHelper进行分页
PageInfo<Orders> pageInfo = ordersService.pageList(page,size);
model.addAttribute("pageInfo",pageInfo);
return "order-list";
}
2.2.5.4 修改Service
/***
* 订单分页查询,使用pageHelper分页插件
* @param page
* @param size
* @return
*/
@Override
public PageInfo<Orders> pageList(int page, int size) {
// 开启分页,
// 第一参数为开始的行号
// 第二参数为获取的记录数
PageHelper.startPage(page,size);
// 查询所有
List<Orders> list = ordersDao.list();
// 根据查询结果创建分页信息并返回
PageInfo<Orders> pageInfo = new PageInfo<Orders>(list);
return pageInfo;
}
2.2.5.6 修改order-list.jsp页面
首先要修改JSP中数据输出
<tbody>
<c:forEach items="${pageInfo.list}" var="order">
<tr>
//...略
</tr>
</c:forEach>
</tbody>
找到分页参数,做一定修改,如下:
<div class="pull-left">
<div class="form-group form-inline">
当前第${pageInfo.pageNum}页/共${pageInfo.pages}页,共${pageInfo.total}条数据。 每页 <select class="form-control" onchange="submitPageSize(this)">
<option>5</option>
<option>10</option>
<option>15</option>
<option>20</option>
<option>50</option>
<option>80</option>
</select> 条
</div>
</div>
<script>
//每页显示条数变化
function submitPageSize(option) {
/**
* 提交查询
* option.value:获取select的值
*/
location.href='/orders/list?page=1&size='+option.value;
}
</script>
<div class="box-tools pull-right">
<ul class="pagination">
<li><a href="/orders/list?page=1&size=${pageInfo.pageSize}" aria-label="Previous">首页</a></li>
<li><a href="/orders/list?page=${pageInfo.pageNum-1}&size=${pageInfo.pageSize}">上一页</a></li>
<c:forEach var="i" begin="1" end="${pageInfo.pages}">
<li><a href="/product/list?page=${i}&size=${pageInfo.pageSize}">${i}</a></li>
</c:forEach>
<li><a href="/orders/list?page=${pageInfo.pageNum+1}&size=${pageInfo.pageSize}">下一页</a></li>
<li><a href="/orders/list?page=${pageInfo.pages}&size=${pageInfo.pageSize}" aria-label="Next">尾页</a></li>
</ul>
</div>
第3章 Spring Security安全框架
3.1 权限管理概述
BRAC的模型:基于角色的访问控制模型。
3.2 Security框架基本介绍
Spring Security 的前身是 Acegi Security ,是 Spring 项目组中用来提供安全认证服务的框架。(https://projects.spring.io/spring-security/)
Spring Security 为基于J2EE企业应用软件提供了全面安全服务。特别是使用领先的J2EE解决方案-Spring框架开发的企业软件项目。人们使用Spring Security有很多种原因,不过通常吸引他们的是在J2EE Servlet规范或EJB规范中找不到典型企业应用场景的解决方案。
特别要指出的是他们不能再WAR 或 EAR 级别进行移植。这样,如果你更换服务器环境,就要,在新的目标环境进行大量的工作,对你的应用系统进行重新配 置安全。使用Spring Security 解决了这些问题,也为你提供很多有用的,完全可以指定的其他安全特性。
安全包括两个主要操作。
-
“认证”,是为用户建立一个他所声明的主体。主题一般指用户,设备或可以在你系 统中执行动作的其他系统。
判断用户是否登陆
-
“授权”指的是一个用户能否在你的应用中执行某个操作,在到达授权判断之前,身份的主题已经由 身份验证过程建立了。
给登陆用户相应的权限
这些概念是通用的,不是Spring Security特有的。在身份验证层面,Spring Security广泛支持各种身份验证模式,这些验证模型绝大多数都由第三方提供,或则正在开发的有关标准机构提供的,例如 Internet Engineering Task Force.作为补充,Spring Security 也提供了自己的一套验证功能。
Spring Security 目前支持认证一体化如下认证技术:
-
HTTP BASIC authentication headers (一个基于IEFT RFC 的标准)
-
HTTP Digest authentication headers (一个基于IEFT RFC 的标准)
-
HTTP X.509 client certificate exchange (一个基于IEFT RFC 的标准)
-
LDAP (一个非常常见的跨平台认证需要做法,特别是在大环境)
-
Form-based authentication (提供简单用户接口的需求)
-
OpenID authentication
-
Computer Associates Siteminder
-
JA-SIG Central Authentication Service (CAS,这是一个流行的开源单点登录系统)
-
Transparent authentication context propagation for Remote Method Invocation and HttpInvoker (一个Spring远程调用协议)
Maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
</dependencies>
3.3 SpringSecurity入门案例
3.3.1 创建一个war包工程
创建一个普通的war包工程,名字可以叫springsecurity-demo,引入相关依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.viking</groupId>
<artifactId>springsecurity-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<!--打包方式war-->
<packaging>war</packaging>
<properties>
<spring.version>5.0.2.RELEASE</spring.version>
<spring.security.version>5.0.1.RELEASE</spring.security.version>
</properties>
<dependencies>
<!--SpringSecurity-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.security.version}</version>
</dependency>
<!--ServletAPI-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<!--
http://localhost:{8888}/{工程名字}
http://localhost:{port}/{path}
-->
<port>8888</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.3.2 配置web.xml
其中监听器主要用于加载配置文件,过滤器用于配置用户拦截,过滤器名字必须为 springSecurityFilterChain
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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>contextConfigLocation</param-name>
<param-value>classpath:spring-security.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
3.3.3 配置spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!--
auto-config:这个是一个自动配置过滤器(Filter)的属性
use-expressions:"是否使用spel表达式",如果使用表达式:hasRole('ROLE_USER')
-->
<security:http auto-config="true" use-expressions="false">
<!-- 配置拦截的请求地址,任何请求地址都必须有ROLE_USER的权限 -->
<security:intercept-url pattern="/**" access="ROLE_USER" />
</security:http>
<!--配置授权信息-->
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<!--
配置账号密码,以及该账号的角色信息
其中{noop}表示加密的类型,noop则表示不使用任何加密方式
PasswordEncoderFactories中有所体现
-->
<security:user name="admin" password="{noop}admin" authorities="ROLE_USER" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
3.3.4 测试
当我们访问index.jsp页面时发现会弹出登录窗口,可能你会奇怪,我们没有建立下面的登录页面,为什么Spring Security会跳到上面的登录页面呢?这是我们设置http的auto-config=”true”时Spring Security自动为我们生成的。
3.3.5 使用自定义页面
我们分别创建一个登录成功页面和登录失败页面以及登录页面,并且实现自定义登录成功、失败、登录等跳转。
3.3.5.1 登录页面 login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="login" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username" /></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password" /></td>
</tr>
<tr>
<td colspan="2" align="center"><input type="submit" value="登录" />
<input type="reset" value="重置" /></td>
</tr>
</table>
</form>
</body>
</html>
3.3.5.2 登录成功页面 success.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
success html<br>
<a href="logout">退出</a>
</body>
</html>
3.3.5.3 登录失败页面 failer.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
登录失败
</body>
</html>
3.3.5.4 自定义页面跳转[配置spring-security.xml]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!--静态资源过滤
pattern 请求的url,
security=none: 不需要认证,所有人都可以访问
-->
<security:http pattern="/login.html" security="none"/>
<security:http pattern="/failer.html" security="none"/>
<!--
auto-config:这个是一个自动配置过滤器(Filter)的属性, 帮我们创建login页面,login认证处理方法
use-expressions:"是否使用spel表达式",如果使用表达式:hasRole('ROLE_USER')
-->
<security:http auto-config="true" use-expressions="false">
<!-- 配置拦截的请求地址,用户要访问任何请求地址都必须有ROLE_USER角色才可以访问 -->
<security:intercept-url pattern="/**" access="ROLE_USER" />
<!--form-login 表单认证
login-page: 登陆页面
login-processing-url: 处理login请求的方法
default-target-url: 默认认证成功后跳转的页面
authentication-failure-url: 认证失败后跳转的页面
username-parameter: 请求过来的用户名参数名称
password-parameter: 请求过来的密码参数名称
authentication-success-forward-url: 认证成功后跳转页面,default-target-url无效了.
-->
<security:form-login
login-page="/login.html"
login-processing-url="/login"
default-target-url="/success.html"
authentication-failure-url="/failer.html"
username-parameter="username"
password-parameter="password"
/>
<!--关闭跨域请求限制-->
<security:csrf disabled="true"/>
<!--invalidate-session: 让session失效, logout-success-url退出成功后跳转的页面
logout-url处理退出登陆的url
-->
<security:logout invalidate-session="true" logout-success-url="/login.html" logout-url="/logout"/>
</security:http>
<!--配置授权信息-->
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<!--
配置账号密码,以及该账号的角色信息
其中{noop}表示加密的类型,noop则表示不使用任何加密方式
PasswordEncoderFactories中有所体现
-->
<security:user name="admin" password="{noop}admin" authorities="ROLE_USER" />
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
</beans>
第4章 搭建用户的开发环境
4.1 创建表结构
--创建用户表
CREATE TABLE sys_user(
id number(10) PRIMARY KEY,
username VARCHAR2(50),
email VARCHAR2(50) ,
PASSWORD VARCHAR2(80),
phoneNum VARCHAR2(20),
STATUS number(1)
);
4.2 创建SysUser
在ssm-domain中创建SysUser
public class SysUser {
private Long id;
private String username;
private String email;
private String password;
private String phoneNum;
private int status;
//get...set...
}
4.3 自定义认证程序
在Spring Security中使用数据库进行认证操作我们可以使用UserDetails、UserDetailsService 来完成操作。
-
UserDetails
public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); }
UserDetails是一个接口,我们可以认为UserDetails作用是封装当前进行认证的用户信息,但由于它是一个
接口,所以我们可以对它进行实现,也可以使用Spring Security提供的一个UserDetails的实现类User来完成
操作
以下是User类的部分代码public class User implements UserDetails, CredentialsContainer { private String password; private final String username; private final Set<GrantedAuthority> authorities;// 用户所拥有的角色或权限集合 private final boolean accountNonExpired; //帐户是否过期 private final boolean accountNonLocked; //帐户是否锁定 private final boolean credentialsNonExpired; //认证是否过期 private final boolean enabled; //帐户是否可用 }
-
UserDetailsService
public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }
UserDetailsService规范了认证的方法,那么在springsecurity进行认证时,就会来调用这个规范的接口方法。
我们将UserDetails与UserDetailsService做了一个简单的介绍,那么我们具体如何完成Spring Security的数据库认
证操作呢,通过用户登录来完成Spring Security的认证操作。
4.3.1 导入依赖包
在ssm-parent中引入依赖管理
<!--统一管理版本-->
<properties>
//...略
<spring.security.version>5.0.1.RELEASE</spring.security.version>
</properties>
<!--依赖管理-->
<dependencyManagement>
<dependencies>
//...略
<!--SpringSecurity-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.security.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
在ssm-service中引入依赖
<!--引入SpringSecurity-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
4.3.2 自定义认证类
在ssm-service中创建UserService接口,让该接口继承UserDetailsService
public interface UserService extends UserDetailsService {
}
创建UserServiceImpl实现UserService,同时实现loadUserByUsername方法,在该方法中写登录认证的代码
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 先设置假的权限
List<GrantedAuthority> authorities = new ArrayList<>();
// 传入角色
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
// 创建用户
User user = new User(username, "{noop}admin", authorities) ;
return user;
}
}
4.3.3 配置SpringSecurity
4.3.3.1 web.xml 过滤器配置
在ssm-web中修改web.xml,加入springsecurity过滤器
<filter>
<!--springSecurityFilterChain 不能修改-->
<filter-name>springSecurityFilterChain</filter-name>
<!--代理过滤器,本身不处理请求,把拦截到请求交 spring容器中一beanName=springSecurityFilterChain来处理-->
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
4.3.3.2 配置spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!--静态资源过滤
pattern 请求的url,
security=none: 不需要认证,所有人都可以访问
-->
<security:http pattern="/*.jsp" security="none"/>
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<security:http pattern="/plugins/**" security="none"/>
<!--
auto-config:这个是一个自动配置过滤器(Filter)的属性, 帮我们创建login页面,login认证处理方法
use-expressions:"是否使用spel表达式",如果使用表达式:hasRole('ROLE_USER')
-->
<security:http auto-config="true" use-expressions="false">
<!-- 配置拦截的请求地址,用户要访问任何请求地址都必须有ROLE_USER角色才可以访问 -->
<security:intercept-url pattern="/**" access="ROLE_USER" />
<!--form-login 表单认证
login-page: 登陆页面
login-processing-url: 处理login请求的方法
default-target-url: 默认认证成功后跳转的页面
authentication-failure-url: 认证失败后跳转的页面
username-parameter: 请求过来的用户名参数
authentication-success-forward-url: 认证成功后跳转页面,default-target-url无效了.
-->
<security:form-login
login-page="/login.jsp"
login-processing-url="/login"
authentication-failure-url="/failer.jsp"
authentication-success-forward-url="/pages/main.jsp"
username-parameter="username"
password-parameter="password"
/>
<!--关闭跨域请求限制-->
<security:csrf disabled="true"/>
<!--invalidate-session: 让session失效, logout-success-url退出成功后跳转的页面
logout-url处理退出登陆的url
-->
<security:logout invalidate-session="true" logout-success-url="/login.jsp" logout-url="/logout"/>
</security:http>
<!--配置授权信息-->
<security:authentication-manager>
<!--使用自定义认证服务-->
<security:authentication-provider user-service-ref="userService">
</security:authentication-provider>
</security:authentication-manager>
</beans>
4.3.3.3 引入springsecurity配置
修改springmvc.xml,添加导入springsecurity.xml
<!--引入SpringSecurity配置-->
<import resource="spring-security.xml" />
4.3.3.4 修改登录地址
修改login.jsp,将action地址改成/login
<form action="${pageContext.request.contextPath}/login" method="post">
//...略
</form>
4.3.3.5 修改index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!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>主页</title>
</head>
<body>
<script>
location.href="/pages/main.jsp";
</script>
</body>
</html>
至此,可以实现登录校验了。
4.4 数据库校验
可以将上述功能写死的账号密码从数据库获取,只需要修改UserServiceImpl,调用Dao从数据库查询即可。
4.4.1 Dao实现数据库查询
创建SysUserDao
package com.viking.dao;
import com.viking.domain.SysUser;
import org.apache.ibatis.annotations.Select;
public interface SysUserDao {
/**
* 通过用户名称查询用户信息
* 认证
* @param username
* @return
*/
@Select("select * from sys_user where username=#{username}")
SysUser findByUsername(String username);
}
4.4.2 调用Dao实现数据库查询账号信息
package com.viking.service.impl;
import com.viking.dao.SysUserDao;
import com.viking.domain.SysUser;
import com.viking.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service("userService")
public class SysUserServiceImpl implements SysUserService {
@Autowired
private SysUserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询数据库认证, 要求:用户名必须唯一
SysUser sysUser = userDao.findByUsername(username);
if(null != sysUser) {
// 登陆用户的权限信息集合
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
//<security:intercept-url pattern="/**" access="ROLE_USER" />
// 创建一个权限信息
SimpleGrantedAuthority sga = new SimpleGrantedAuthority("ROLE_USER");
// 放入集合中
authorities.add(sga);
// spring security 规范好了的登陆用户信息
// {noop}让security不要加密
User user = new User(sysUser.getUsername(),"{noop}" + sysUser.getPassword(),authorities);
return user;
}
return null;
}
}