PageHelper&SpringSecurity

第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 分页插件参数介绍
  1. helperDialect:分页插件会自动检测当前的数据库链接,自动选择合适的分页方式。 你可以配置helperDialect属性来指定分页插件使用哪种方言。配置时,可以使用下面的缩写值:
    oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby

特别注意:使用 SqlServer2012 数据库时,需要手动指定为 sqlserver2012,否则会使用 SqlServer2005 的方式进行分页。
你也可以实现 AbstractHelperDialect,然后配置该属性为实现类的全限定名称即可使用自定义的实现方法。

  1. offsetAsPageNum:默认值为 false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为 true 时,会将 RowBounds 中的 offset 参数当成 pageNum 使用,可以用页码和页面大小两个参数进行分页。

  2. rowBoundsWithCount:默认值为false,该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为true时,使用 RowBounds 分页会进行 count 查询。

  3. pageSizeZero:默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果(相当于没有执行分页查询,但是返回结果仍然是 Page 类型)。

  4. reasonable:分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。

  5. params:为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值, 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。

  6. supportMethodsArguments:支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。 使用方法可以参考测试代码中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTest 和 ArgumentsObjTest。

  7. autoRuntimeDialect:默认值为 false。设置为 true 时,允许在运行时根据多数据源自动识别对应方言的分页 (不支持自动选择sqlserver2012,只能使用sqlserver),用法和注意事项参考下面的场景五。

  8. 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;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值