SpringBootWeb案例-登录认证

HM大约 58 分钟SpringBoot登录过滤器拦截器JWT令牌Token异常SessionCookie·@ServletComponentScan·@WebFilter

SpringBootWeb案例-登录认证

今日目标

目标

  • 能够编写案例中的登录接口 ❤️✏️
  • 能够理解会话技术Cookie和Session 🍐
  • 能够理解JWT令牌的作用和掌握其使用 🍐❤️✏️
  • 能够理解并掌握过滤器Filter 🍐 ❤️
  • 能够区分拦截器和过滤器的区别 🍐
  • 能够掌握统一异常处理 🍐 ✏️

知识储备

  1. 已经完成
    1. 部门管理
    2. 员工管理
  2. 能理解添加员工和修改员工需要权限管理
  3. 有过登陆某些网站,在接下的操作中,可以已登录状态进行访问
  4. 能理解 "服务器正忙,请稍后的再试!"的含义

1. 登录功能 🚩

登录功能

image-20230105085404855
image-20230105085404855

在登录界面中,我们可以输入用户的用户名以及密码,然后点击 "登录" 按钮就要请求服务器,服务端判断用户输入的用户名或者密码是否正确。如果正确,则返回成功结果,前端跳转至系统首页面。

代码操作

登录服务端的核心逻辑:

  1. 接收前端请求传递的用户名和密码(数据库中的密码是密文,还是明文)
  2. 然后再根据用户名和密码查询用户信息,
    • 如果用户信息存在,则说明用户输入的用户名和密码正确。
    • 如果查询到的用户不存在,则说明用户输入的用户名和密码错误。
image-20230105175310401
image-20230105175310401

LoginController

@RestController
public class LoginController {

    @Autowired
    private EmpService empService;

    @PostMapping("/login")
    public Result login(@RequestBody Emp emp){
        Emp e = empService.login(emp);
	    return  e != null ? Result.success():Result.error("用户名或密码错误");
    }
}







 

 


总结

课堂作业

  1. 根据接口文档,独自完成上述的登陆接口代码 ✏️
  2. 登录的作用是什么? 晚上上述登录代码,完成登录校验了吗?

2. 登录校验 🍐 ❤️

2.1 登录技术分析

登录校验

问题 :登录和未登录都能访问后端系统页面

期望功能:登陆后才能访问后端系统页面,不登陆则跳转登陆页面进行登陆 🎯

原因: 登录状态没有被记录(思考:HTTP协议的特点)

解决方案:登录校验image-20230105180811717

2.2 会话技术

会话技术

1.会话技术的作用:

由于HTTP是无状态协议,在后面请求中怎么拿到前一次请求生成的数据呢?此时就需要在一次会话的多次请求之间进行数据共享

👉 在我们日常生活当中,会话指的就是谈话、交谈。

👉 在web开发当中,会话指的就是浏览器与服务器之间的一次连接,我们就称为一次会话。

2.会话是何时建立何时销毁: 👇

在用户打开浏览器第一次访问服务器的时候,这个会话就建立了 ,直到有任何一方断开连接,此时会话就结束了。在一次会话当中,是可以包含多次请求和响应的。

比如:打开了浏览器来访问web服务器上的资源(浏览器不能关闭、服务器不能断开)

  • 第1次:访问的是登录的接口,完成登录操作
  • 第2次:访问的是部门管理接口,查询所有部门数据
  • 第3次:访问的是员工管理接口,查询员工数据

只要浏览器和服务器都没有关闭,以上3次请求都属于一次会话当中完成的。 image-20230105203827355

3.需要注意的是:

  1. 会话是和浏览器关联的,当有三个浏览器客户端和服务器建立了连接时,就会有三个会话。
  2. 同一个浏览器在未关闭之前请求了多次服务器,这多次请求是属于同一个会话。比如:1、2、3这三个请求都是属于同一个会话。
  3. 当我们关闭浏览器 之后,这次会话就结束了。而如果我们是直接把web服务器关了,那么所有的会话就都结束了。

客户端会话跟踪技术-Cookie

Cookie 是客户端会话跟踪技术,它是存储在客户端浏览器的,我们使用 cookie 来跟踪会话,我们就可以在浏览器第一次发起请求来请求服务器的时候,我们在服务器端来设置一个cookie

优缺点 👇

  • 优点:HTTP协议中支持的技术(像Set-Cookie 响应头的解析以及 Cookie 请求头数据的携带,都是浏览器自动进行的,是无需我们手动操作的)
  • 缺点
    • 移动端APP(Android、IOS)中无法使用Cookie
    • 不安全,用户可以自己禁用Cookie
    • Cookie不能跨域

代码操作

步骤

  1. 定义一个接口,用来设置cookie到响应头
  2. 定义一个接口,用来打印请求头中的cookie值,是否为需求1的设置值
  3. 打开浏览器,按F12->Network 观察请求和响应中的Cookie值
@Slf4j
@RestController
public class SessionController {

    //设置Cookie
    @GetMapping("/c1")
    public Result cookie1(HttpServletResponse response){
        response.addCookie(new Cookie("login_username","itheima")); //设置Cookie/响应Cookie
        return Result.success();
    }
	
    //获取Cookie
    @GetMapping("/c2")
    public Result cookie2(HttpServletRequest request){
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            if(cookie.getName().equals("login_username")){
                System.out.println("login_username: "+cookie.getValue()); //输出name为login_username的cookie
            }
        }
        return Result.success();
    }
}    






 
 





 
 








总结

课堂作业

  1. Cookie是通过什么设置到浏览器中的?
  2. 如果浏览器不关闭,请问访问该网站的不同请求,服务器怎么能辨别是同一个浏览器发起的请求(同一个会话)
  3. 🚩 打开浏览器,访问百度首页,按F12+Network 查看请求头信息。
    1. 查看Cookie存在的位置(请求行,请求头),
    2. 点击Application Tab栏目,观察是否存在很多Cookie?并选中其中一个Cookie 尝试删除Cookie
    3. 截取第二步的Cookie,和第一步的Cookie对比一下,观察是否相同,并观察多个Cookie用什么符号隔开的。

2.4 会话跟踪方案- Session 🍐

前言

Session是服务器端会话跟踪技术,所以它是存储在服务器端 的。而 Session 的底层其实就是基于我们刚才所介绍的 Cookie 来实现的。

优缺点👇 👇

  • 优点:Session是存储在服务端的,安全 👍 👍
  • 缺点:
    • 服务器集群环境下无法直接使用Session 😢
    • 移动端APP(Android、IOS)中无法使用Cookie 😢
    • 用户可以自己禁用Cookie 😢
    • Cookie不能跨域 😢

PS:Session 底层是基于Cookie实现的会话跟踪,如果Cookie不可用,则该方案,也就失效了

Session使用 👇 👇

  • 1. 获取Session

    image-20230112105938545
    image-20230112105938545

    如果我们现在要基于 Session 来进行会话跟踪,浏览器在第一次请求服务器的时候,我们就可以直接在服务器当中来获取到会话对象Session。如果是第一次请求Session ,会话对象是不存在的,这个时候服务器会自动的创建一个会话对象Session 。而每一个会话对象Session ,它都有一个ID(示意图中Session后面括号中的1,就表示ID),我们称之为 Session 的ID。

  • 2. 响应Cookie (JSESSIONID)

    image-20230112110441075
    image-20230112110441075

    接下来,服务器端在给浏览器响应数据的时候,它会将 Session 的 ID 通过 Cookie 响应给浏览器。其实在响应头当中增加了一个 Set-Cookie 响应头。这个 Set-Cookie 响应头对应的值是不是cookie? cookie 的名字是固定的 JSESSIONID 代表的服务器端会话对象 Session 的 ID 。浏览器会自动识别这个响应头,然后自动将Cookie存储在浏览器本地。

  • 3. 查找Session

    image-20230112101943835
    image-20230112101943835

    接下来,在后续的每一次请求当中,都会将 Cookie 的数据获取出来,并且携带到服务端。接下来服务器拿到JSESSIONID这个 Cookie 的值,也就是 Session 的ID。拿到 ID 之后,就会从众多的 Session 当中来找到当前请求对应的会话对象Session。

总结

开发实际应用

  1. Cookie和Session在现在的企业开发当中是不是会存在很多的问题 👎
    1. Cookie存储在浏览器,不安全,数据有限
    2. Session存储在服务器,占内存,不能跨域
  2. 在现在的企业开发当中,基本上都会采用第三种方案--令牌技术 👍

2.5 会话跟踪方案-令牌技术 🍐 ❤️

令牌技术

令牌,其实它就是一个用户身份的标识,看似很高大上,很神秘,其实本质就是一个字符串。

image-20230112102022634
image-20230112102022634
  1. 如果通过令牌技术来跟踪会话,我们就可以在浏览器发起请求。在请求登录接口的时候,如果登录成功,我就可以生成一个令牌,令牌就是用户的合法身份凭证。接下来我在响应数据的时候,我就可以直接将令牌响应给前端。

  2. 登录成功后,在前端程序当中接收到令牌之后,就需要将这个令牌存储起来

    1. 可以存储在 cookie 当中
    2. 也可以存储在其他的存储空间(比如:localStorage)当中
  3. 接下来,在后续的每一次请求当中,都需要将令牌携带到服务端。携带到服务端之后,接下来我们就需要来校验令牌的有效性。如果令牌是有效的,就说明用户已经执行了登录操作,如果令牌是无效的,就说明用户之前并未执行登录操作。

此时,如果是在同一次会话的多次请求之间,我们想共享数据,我们就可以将共享的数据存储在令牌当中就可以了。

优缺点

  • 优点
    • 支持PC端、移动端 👍
    • 解决集群环境下的认证问题 👍
    • 减轻服务器的存储压力(无需在服务器端存储) 👍
  • 缺点:需要自己实现(包括令牌的生成、令牌的传递、令牌的校验)

后续的课程会采用令牌技术来解决案例项目当中的会话跟踪问题 👈

作业

  1. 🚩 能够流畅的说出Cookie和Session的特点,以及Session和Cookie的关系,以及企业开发中为何使用令牌技术

2.6 JWT令牌 🍐 ❤️

前言

JWT全称:JSON Web Token (官网:https://jwt.io/)open in new window

  • 定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在,这些信息是可靠的。

    简洁:是指jwt就是一个简单的字符串。可以在请求参数或者是请求头当中直接传递。

    自包含:指的是jwt令牌,看似是一个随机的字符串,但是我们是可以根据自身的需求在jwt令牌中存储自定义的数据内容。如:可以直接在jwt令牌中存储用户的相关信息。

代码操作

简单介绍了JWT令牌以及JWT令牌的组成之后,接下来我们就来学习基于Java代码如何。

1️⃣ 首先我们先来实现JWT令牌的生成。要想使用JWT令牌,需要先引入JWT的依赖
<!-- JWT依赖-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

在引入完JWT来赖后,就可以调用工具包中提供的API来完成JWT令牌的生成和校验

工具类:Jwts

总结

课堂作业

  1. jwt的组成部分?🎤
  2. JWT是如何将原始的JSON格式数据,转变为字符串的呢?🎤
  3. JWT令牌是非常安全可靠的,从何谈起?🎤

2.7 JWT集成登陆模块 ✏️

JWT集成登陆模块

JWT令牌的生成和校验的基本操作我们已经学习完了,接下来我们就需要在案例当中通过JWT令牌技术来跟踪会话。具体的思路我们前面已经分析过了,主要就是两步操作: 👇 👇

  1. 生成令牌
    • 在登录成功之后来生成一个JWT令牌,并且把这个令牌直接返回给前端 👈
  2. 校验令牌
    • 拦截前端请求,从请求中获取到令牌,对令牌进行解析校验

代码实现

解读完接口文档中的描述了,目前我们先来完成令牌的生成和令牌的下发,我们只需要生成一个令牌返回给前端就可以了。

步骤如下:

  1. 必须先完成登陆功能(第一个功能)
  2. 引入JWT工具类
    • 在项目工程下创建com.itheima.utils包,并把提供JWT工具类复制到该包下
  3. 登录完成后,调用工具类生成JWT令牌并返回

JWT工具类

util包中

public class JwtUtils {

    private static String signKey = "itheima";//签名密钥
    private static Long expire = 43200000L; //有效时间

    /**
     * 生成JWT令牌
     * @param claims JWT第二部分负载 payload 中存储的内容
     * @return
     */
    public static String generateJwt(Map<String, Object> claims){
        String jwt = Jwts.builder()
                .addClaims(claims)//自定义信息(有效载荷)
                .signWith(SignatureAlgorithm.HS256, signKey)//签名算法(头部)
                .setExpiration(new Date(System.currentTimeMillis() + expire))//过期时间
                .compact();
        return jwt;
    }

    /**
     * 解析JWT令牌
     * @param jwt JWT令牌
     * @return JWT第二部分负载 payload 中存储的内容
     */
    public static Claims parseJWT(String jwt){
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)//指定签名密钥
                .parseClaimsJws(jwt)//指定令牌Token
                .getBody();
        return claims;
    }
}

作业

  1. 🚩 完成上述登录逻辑后,查看资料和笔记,找到存在浏览器中的令牌位置,以及观察每次请求是怎样携带令牌的

2.8 过滤器Filter 🍐

过滤器Filter

  • Filter表示过滤器,是 JavaWeb三大组件(Servlet、Filter、Listener)之一。
  • 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能
    • 使用了过滤器之后,要想访问web服务器上的资源,必须先经过滤器,过滤器处理完毕之后,才可以访问对应的资源
  • 过滤器一般完成一些通用的操作,比如:登录校验、统一编码处理、敏感字符处理等。
image-20230112120955145
image-20230112120955145

过滤器的基本使用操作:

  • 第1步,定义过滤器 :1.定义一个类,实现 Filter 接口,并重写其所有方法。
  • 第2步,配置过滤器:Filter类上加 @WebFilter 注解,配置拦截资源的路径。引导类上加 @ServletComponentScan 开启Servlet组件支持。

代码操作

定义过滤器

//定义一个类,实现一个标准的Filter过滤器的接口
public class DemoFilter implements Filter {
    @Override //初始化方法, 只调用一次
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init 初始化方法执行了");
    }

    @Override //拦截到请求之后调用, 调用多次
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Demo 拦截到了请求...放行前逻辑");
        //放行
        chain.doFilter(request,response);
    }

    @Override //销毁方法, 只调用一次
    public void destroy() {
        System.out.println("destroy 销毁方法执行了");
    }
}

 

















  • init方法:过滤器的初始化方法。在web服务器启动的时候会自动的创建Filter过滤器对象,在创建过滤器对象的时候会自动调用init初始化方法,这个方法只会被调用一次。

  • doFilter方法:这个方法是在每一次拦截到请求之后都会被调用,所以这个方法是会被调用多次的,每拦截到一次请求就会调用一次doFilter()方法。

  • destroy方法: 是销毁的方法。当我们关闭服务器的时候,它会自动的调用销毁方法destroy,而这个销毁方法也只会被调用一次。

恭喜你完成 Filter过滤器的基本使用 🎉,下面我们将学习Filter过滤器在使用过程中的一些细节。

2.9 过滤器Filter过滤器路径配置和执行流程 🍐

过滤器路径配置和执行流程

学习过滤器Filter在使用中的一些细节:

  1. 过滤器的执行流程
  2. 过滤器的拦截路径配置
  3. 过滤器链
1️⃣ 执行流程 🍐

首先我们先来看下过滤器的执行流程:

image-20230106222559935
image-20230106222559935

过滤器当中我们拦截到了请求之后,如果希望继续访问后面的web资源,就要执行放行操作,放行就是调用 FilterChain对象当中的doFilter()方法,在调用doFilter()这个方法之前所编写的代码属于放行之前的逻辑。

在放行后访问完 web 资源之后还会回到过滤器当中,回到过滤器之后如有需求还可以执行放行之后的逻辑,放行之后的逻辑我们写在doFilter()这行代码之后。

拦截路径代码操作

下面我们来测试"拦截所有 ":

@WebFilter(urlPatterns = "/*") 
public class DemoFilter implements Filter {
    
    @Override //初始化方法, 只调用一次
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init 初始化方法执行了");
    }
    
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        
        System.out.println("DemoFilter   放行前逻辑.....");

        //放行请求
        filterChain.doFilter(servletRequest,servletResponse);

        System.out.println("DemoFilter   放行后逻辑.....");
        
    }

    @Override //销毁方法, 只调用一次
    public void destroy() {
        System.out.println("destroy 销毁方法执行了");
    }
}
 










 
 
 
 
 
 








image-20230106224322625
image-20230106224322625

2.10 过滤器链 🚀

过滤器链

过滤器链指的是在一个web应用程序当中,可以配置多个过滤器,多个过滤器就形成了一个过滤器链。

image-20230107084730393
image-20230107084730393

比如:在我们web服务器当中,定义了两个过滤器,这两个过滤器就形成了一个过滤器链。

而这个链上的过滤器在执行的时候会一个一个的执行,会先执行第一个Filter,放行之后再来执行第二个Filter,如果执行到了最后一个过滤器放行之后,才会访问对应的web资源。

访问完web资源之后,按照我们刚才所介绍的过滤器的执行流程,还会回到过滤器当中来执行过滤器放行后的逻辑,而在执行放行后的逻辑的时候,顺序是反着的。

先要执行过滤器2放行之后的逻辑,再来执行过滤器1放行之后的逻辑,最后在给浏览器响应数据。

以上就是当我们在web应用当中配置了多个过滤器,形成了这样一个过滤器链以及过滤器链的执行顺序。下面我们通过idea来验证下过滤器链。

验证步骤:

  1. 在filter包下再来新建一个Filter过滤器类:AbcFilter
  2. 在AbcFilter过滤器中编写放行前和放行后逻辑
  3. 配置AbcFilter过滤器拦截请求路径为:/*
  4. 重启SpringBoot服务,查看DemoFilter、AbcFilter的执行日志 image-20230107085552176

AbcFilter过滤器

@WebFilter(urlPatterns = "/*")
public class AbcFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Abc 拦截到了请求... 放行前逻辑");

        //放行
        chain.doFilter(request,response);

        System.out.println("Abc 拦截到了请求... 放行后逻辑");
    }
}

总结

过滤器链指的是在一个web应用程序当中,可以配置多个过滤器,多个过滤器就形成了一个过滤器链。

image-20230107084730393
image-20230107084730393

课堂作业

  1. 过滤器链有哪些应用场景🎤

到此,关于过滤器的使用细节,我们已经全部介绍完毕了。🎉

2.11 登录校验-Filter ✏️

登录校验-Filter

  1. 所有的请求,拦截到了之后,都需要校验令牌吗?

答案:登录请求例外

  1. 拦截到请求后,什么情况下才可以放行,执行业务操作?

答案:有令牌,且令牌校验通过(合法);否则都返回未登录错误结果

要完成登录校验,主要是利用Filter过滤器实现,而Filter过滤器的流程步骤

  1. 获取请求url
  2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行
  3. 获取请求头中的令牌(token)
  4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)
  5. 解析token,如果解析失败,返回错误结果(未登录)
  6. 放行
image-20230112122130564
image-20230112122130564
代码实现

导入第三方json处理的工具包fastjson。我们要想使用,需要引入如下依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

作业

  1. 🚩 能够说出SpringBoot工程中使用过滤器涉及到哪几个注解?分别的作用?以及通过流程图绘制出登录案例中的过滤器的业务流程。

2.12 拦截器Interceptor 🍐

  • 目标1🎯 :了解什么是拦截器,并通过快速入门程序上手拦截器 🍐
  • 目标2🎯 :了解拦截器的使用细节 🍐
  • 目标3🎯 :通过拦截器Interceptor完成登录校验功能 ✏️

2.12.1 拦截器快速入门

前言

拦截器:

  • 是一种动态拦截方法调用的机制,类似于过滤器。
  • 拦截器是Spring框架中提供的,用来动态拦截控制器方法的执行

拦截器的作用:

  • 拦截请求,在指定方法调用前后,根据业务需要执行预先设定的代码。

在拦截器当中,我们通常也是做一些通用性的操作,比如:我们可以通过拦截器来拦截前端发起的请求,将登录校验的逻辑全部编写在拦截器当中。在校验的过程当中,如发现用户登录了(携带JWT令牌且是合法令牌),就可以直接放行,去访问spring当中的资源。如果校验时发现并没有登录或是非法令牌,就可以直接给前端响应未登录的错误信息。

代码操作

集成步骤

  1. 自定义拦截器:实现HandlerInterceptor接口,并重写其所有方法
  2. 注册配置拦截器 :创建一个config包,创建一个类,实现WebMvcConfigurer接口,并重写addInterceptors方法
    1. 配置拦截器的拦截路径

自定义拦截器:实现HandlerInterceptor接口,并重写其所有方法

//自定义拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    //目标资源方法执行前执行。 返回true:放行    返回false:不放行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle .... ");
        
        return true; //true表示放行
    }

    //目标资源方法执行后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle ... ");
    }

    //视图渲染完毕后执行,最后执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion .... ");
    }
}
 
 






 














注意:

​ preHandle方法:目标资源方法执行前执行。 返回true:放行 返回false:不放行

​ postHandle方法:目标资源方法执行后执行

​ afterCompletion方法:视图渲染完毕后执行,最后执行

总结

课堂作业

  1. 什么是拦截器,拦截器有什么作用?🎤

2.12.2 Interceptor详解

Interceptor详解

  1. 拦截器的拦截路径/**/*有什么区别?
拦截路径含义举例
/*一级路径能匹配/depts,/emps,/login,不能匹配 /depts/1
/**任意级路径能匹配/depts,/depts/1,/depts/1/2
/depts/*/depts下的一级路径能匹配/depts/1,不能匹配/depts/1/2,/depts
/depts/**/depts下的任意级路径能匹配/depts,/depts/1,/depts/1/2,不能匹配/emps/1
  1. 拦截器和过滤器同时存在,哪个先执行

先执行过滤器,再执行拦截器 image-20210805175445422

代码操作

  1. 演示下/**/*的区别
  2. 配置拦截路径

下面主要来演示下/**/*的区别:

  • 修改拦截器配置,把拦截路径设置为/*
@Configuration 
public class WebConfig implements WebMvcConfigurer {

    //拦截器对象
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       //注册自定义拦截器对象
        registry.addInterceptor(loginCheckInterceptor)
                .addPathPatterns("/*")
                .excludePathPatterns("/login");//设置不拦截的请求路径
    }
}

使用postman测试:http://localhost:8080/emps/1open in new window

image-20230107111525558
image-20230107111525558

控制台没有输出拦截器中的日志信息,说明/*没有匹配到拦截路径/emp/1

image-20230107111812963
image-20230107111812963

2.12.3 执行流程

前言

介绍完拦截路径的配置之后,接下来我们再来介绍拦截器的执行流程。通过执行流程,大家就能够清晰的知道过滤器与拦截器的执行时机。

image-20230107112136151
image-20230107112136151
  • 当我们打开浏览器来访问部署在web服务器当中的web应用时,此时我们所定义的过滤器会拦截到这次请求。拦截到这次请求之后,它会先执行放行前的逻辑,然后再执行放行操作。而由于我们当前是基于springboot开发的,所以放行之后是进入到了spring的环境当中,也就是要来访问我们所定义的controller当中的接口方法。

  • Tomcat并不识别所编写的Controller程序,但是它识别Servlet程序,所以在Spring的Web环境中提供了一个非常核心的Servlet:DispatcherServlet(前端控制器),所有请求都会先进行到DispatcherServlet,再将请求转给Controller。

  • 当我们定义了拦截器后,会在执行Controller的方法之前,请求被拦截器拦截住。执行preHandle()方法,这个方法执行完成后需要返回一个布尔类型的值,如果返回true,就表示放行本次操作,才会继续访问controller中的方法;如果返回false,则不会放行(controller中的方法也不会执行)。

  • 在controller当中的方法执行完毕之后,再回过来执行postHandle()这个方法以及afterCompletion() 方法,然后再返回给DispatcherServlet,最终再来执行过滤器当中放行后的这一部分逻辑的逻辑。执行完毕之后,最终给浏览器响应数据。

代码操作

接下来我们就来演示下过滤器和拦截器同时存在的执行流程:

  • 开启LoginCheckInterceptor拦截器
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle .... ");
        
        return true; //true表示放行
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle ... ");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion .... ");
    }
}
@Configuration  
public class WebConfig implements WebMvcConfigurer {

    //拦截器对象
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册自定义拦截器对象
        registry.addInterceptor(loginCheckInterceptor)
                .addPathPatterns("/**")//拦截所有请求
                .excludePathPatterns("/login");//不拦截登录请求
    }
}

总结

  1. 过滤器和拦截器之间的区别
    • 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。
    • 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。

课堂作业

  1. 过滤器和拦截器之间的区别?🎤

2.12.4 登录校验- Interceptor ✏️

登录校验- Interceptor

目标🎯 :通过拦截器来完成案例当中的登录校验功能

登录校验的业务逻辑以及操作步骤和登录校验Filter过滤器当中的逻辑是完全一致的。现在我们只需要把这个技术方案由原来的过滤器换成拦截器interceptor就可以了。

代码操作

登录校验拦截器(直接复制过滤器核心代码)

//自定义拦截器
@Component //当前拦截器对象由Spring创建和管理
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {
    //前置方式
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle .... ");
        //1.获取请求url
        //2.判断请求url中是否包含login,如果包含,说明是登录操作,放行

        //3.获取请求头中的令牌(token)
        String token = request.getHeader("token");
        log.info("从请求头中获取的令牌:{}",token);

        //4.判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if(!StringUtils.hasLength(token)){
            log.info("Token不存在");

            //创建响应结果对象
            Result responseResult = Result.error("NOT_LOGIN");
            //把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
            String json = JSONObject.toJSONString(responseResult);
            //设置响应头(告知浏览器:响应的数据类型为json、响应的数据编码表为utf-8)
            response.setContentType("application/json;charset=utf-8");
            //响应
            response.getWriter().write(json);

            return false;//不放行
        }

        //5.解析token,如果解析失败,返回错误结果(未登录)
        try {
            JwtUtils.parseJWT(token);
        }catch (Exception e){
            log.info("令牌解析失败!");

            //创建响应结果对象
            Result responseResult = Result.error("NOT_LOGIN");
            //把Result对象转换为JSON格式字符串 (fastjson是阿里巴巴提供的用于实现对象和json的转换工具类)
            String json = JSONObject.toJSONString(responseResult);
            //设置响应头
            response.setContentType("application/json;charset=utf-8");
            //响应
            response.getWriter().write(json);

            return false;
        }

        //6.放行
        return true;
    }

3. 异常处理 🍐

异常处理

  1. 那么在三层构架项目中,出现了异常,该如何处理?
  • 方案一:在所有Controller的所有方法中进行try…catch处理 不建议用
    • 缺点:代码臃肿(不推荐)
  • 方案二:全局异常处理器企业开发实用
    • 好处:简单、优雅(推荐)
image-20230107122904214
image-20230107122904214

总结

全局异常处理主要涉及到两个注解:

  • @RestControllerAdvice(修饰类) //表示当前类为全局异常处理器
  • @ExceptionHandler(修饰方法) //指定可以捕获哪种类型的异常进行处理

课堂作业

  1. 🚩 单独创建一个专门拦截空指针异常的方法,并返回消息("服务器忙着了尼,请稍后再试")
  2. 🚩 查阅资料,自定义一个异常,并且在异常处理类进行捕获

课后作业

🚩 1. 重点完成上述的课堂作业

  1. 晚自习第一节课的前30分钟,总结完毕之后,每个同学先必须梳理今日知识点 (记得写不知道的,以及感恩三件事);整理好的笔记可以发给组长,组长交给班长,意在培养大家总结的能力)

  2. 晚自习第一节课的后30分钟开始练习(记住:程序员是代码堆起来的):

    • 先要把今天的所有案例或者课堂练习,如果没练完的,练完他
    • 独立书写今日作业 Day12作业👈
  3. 剩余的时间:预习第二天的知识,预习的时候一定要注意:

  • 预习不是学习,不要死看第二天的视频(很容易出现看了白看,为了看视频而看视频)
  • 预习看第二天的笔记,把笔记中标注重要的知识,可以找到预习视频,先看一遍,如果不懂的 ,记住做好标注。
上次编辑于:
贡献者: huhu520,itcast