SpringBoot-登录校验-过滤器-拦截器-JWT

// 后面用(统一管理类)
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
    /**
     * 响应码,1 代表成功; 0 代表失败
     */
    private Integer code;
    /**
     * 响应信息 描述字符串
     */
    private String msg;
    /**
     * 返回的数据
     */
    private Object data;

    /**
     * 增删改 成功响应
     */

    public static Result success() {
        return new Result(1, "success", null);
    }

    /**
     * 查询 成功响应
     * @param data
     * @return
     */
    public static Result success(Object data) {
        return new Result(1, "success", data);
    }

    /**
     * 失败响应
     * @param msg
     * @return
     */
    public static Result error(String msg) {
        return new Result(0, msg, null);
    }
}

登录校验

什么是登录校验?

  • 所谓登录校验,指的是我们在服务器端接收到浏览器发送过来的请求之后,首先我们要对请求进行校验。先要校验一下用户登录了没有,如果用户已经登录了,就直接执行对应的业务操作就可以了;如果用户没有登录,此时就不允许他执行相关的业务操作,直接给前端响应一个错误的结果,最终跳转到登录页面,要求他登录成功之后,再来访问对应的数据。

首先我们在宏观上先有一个认知:

前面在讲解HTTP协议的时候,我们提到HTTP协议是无状态协议。什么又是无状态的协议?

  • 所谓无状态,指的是每一次请求都是独立的,下一次请求并不会携带上一次请求的数据。而浏览器与服务器之间进行交互,基于HTTP协议也就意味着现在我们通过浏览器来访问了登陆这个接口,实现了登陆的操作,接下来我们在执行其他业务操作时,服务器也并不知道这个员工到底登陆了没有。因为HTTP协议是无状态的,两次请求之间是独立的,所以是无法判断这个员工到底登陆了没有。

在这里插入图片描述

那应该怎么来实现登录校验的操作呢?具体的实现思路可以分为两部分:

  1. 在员工登录成功后,需要将用户登录成功的信息存起来,记录用户已经登录成功的标记。
  2. 在浏览器发起请求时,需要在服务端进行统一拦截,拦截后进行登录校验。
  • 想要判断员工是否已经登录,我们需要在员工登录成功之后,存储一个登录成功的标记,接下来在每一个接口方法执行之前,先做一个条件判断,判断一下这个员工到底登录了没有。如果是登录了,就可以执行正常的业务操作,如果没有登录,会直接给前端返回一个错误的信息,前端拿到这个错误信息之后会自动的跳转到登录页面。
  • 我们程序中所开发的查询功能、删除功能、添加功能、修改功能,都需要使用以上套路进行登录校验。此时就会出现:相同代码逻辑,每个功能都需要编写,就会造成代码非常繁琐。
  • 为了简化这块操作,我们可以使用一种技术:统一拦截技术。
  • 通过统一拦截的技术,我们可以来拦截浏览器发送过来的所有的请求,拦截到这个请求之后,就可以通过请求来获取之前所存入的登录标记,在获取到登录标记且标记为登录成功,就说明员工已经登录了。如果已经登录,我们就直接放行(意思就是可以访问正常的业务接口了)。

在这里插入图片描述

1、会话技术

1. 概述

会话:用户打开一个浏览器,点击了很多超链接,访问多个web资源,关闭浏览器,这个过程可以称之为会话
有状态会话:一个同学来过教室,下次再来教室,我们会知道这个同学曾经来过,称之为有状态会话;

你能怎么证明你是师大的学生?
          你              师大
​ 1、通知书 师大给你通知书
​ 2、学校登记 师大标记你来过了

一个网站,怎么证明你来过?

客户端             服务端

  1. 服务端给客户端一个信件,客户端下次访问服务器带上信件就可以了; cookie
  2. 服务器登记你来过了,下次你来的时候我来匹配你;

在这里插入图片描述

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

会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

服务器会接收很多的请求,但是服务器是需要识别出这些请求是不是同一个浏览器发出来的。比如:1和2这两个请求是不是同一个浏览器发出来的,3和5这两个请求不是同一个浏览器发出来的。如果是同一个浏览器发出来的,就说明是同一个会话。如果是不同的浏览器发出来的,就说明是不同的会话。而识别多次请求是否来自于同一浏览器的过程,我们就称为会话跟踪。

我们使用会话跟踪技术就是要完成在同一个会话中,多个请求之间进行共享数据。

为什么要共享数据呢?

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

会话跟踪方案

  • 客户端会话跟踪技术:Cookie
  • 服务端会话跟踪技术:Session
  • 令牌技术:Token

2. Cookie

  • 客户端技术(响应,请求)
  • 服务器端在给客户端在响应数据的时候,会自动的将 cookie 响应给浏览器,浏览器接收到响应回来的 cookie 之后,会自动的将 cookie 的值存储在浏览器本地。接下来在后续的每一次请求当中,都会将浏览器本地所存储的 cookie 自动地携带到服务端。

在这里插入图片描述

  • 服务器会 自动 的将 cookie 响应给浏览器。
  • 浏览器接收到响应回来的数据之后,会 自动 的将 cookie 存储在浏览器本地。
  • 在后续的请求当中,浏览器会 自动 的将 cookie 携带到服务器端。

为什么这一切都是自动化进行的?
是因为 cookie 它是 HTTP 协议当中所支持的技术,而各大浏览器厂商都支持了这一标准。在 HTTP 协议官方给我们提供了一个响应头和请求头:

  • 响应头 Set-Cookie :设置Cookie数据的
  • 请求头 Cookie:携带Cookie数据的

在这里插入图片描述

优点:HTTP协议中支持的技术

缺点:

  1. 移动端APP无法使用Cookie
  2. 不安全,用户可以自己禁用Cookie
  3. Cookie不能跨域

跨域介绍:
在这里插入图片描述

  • 现在的项目,大部分都是前后端分离的,前后端最终也会分开部署,前端部署在服务器 192.168.150.200 上,端口 80,后端部署在 192.168.150.100上,端口 8080
  • 我们打开浏览器直接访问前端工程,访问url:http://192.168.150.200/login.html
  • 然后在该页面发起请求到服务端,而服务端所在地址不再是localhost,而是服务器的IP地址192.168.150.100,假设访问接口地址为:http://192.168.150.100:8080/login
  • 那此时就存在跨域操作了,因为我们是在http://192.168.150.200/login.html这个页面上访问了http://192.168.150.100:8080/login 接口
  • 此时如果服务器设置了一个Cookie,这个Cookie是不能使用的,因为Cookie无法跨域

区分跨域的维度:

  • 协议
  • IP/协议
  • 端口

只要上述的三个维度有任何一个维度不同,那就是跨域操作

举例:

​ http://192.168.150.200/login.html ----------> https://192.168.150.200/login [协议不同,跨域]

​ http://192.168.150.200/login.html ----------> http://192.168.150.100/login [IP不同,跨域]

​ http://192.168.150.200/login.html ----------> http://192.168.150.200:8080/login [端口不同,跨域]

​ http://192.168.150.200/login.html ----------> http://192.168.150.200/login [不跨域]

@Slf4j
@RestController
public class SessionController {
    /**
     * 设置Cookie
     * @param response
     * @return
     */
    @GetMapping("/c1")
    public Result cookie1(HttpServletResponse response) {
        // 设置Cookie/响应Cookie
        response.addCookie(new Cookie("login_username", "xiaokoen"));
        return Result.success();
    }

    /**
     * 获取Cookie
     *
     * @param request
     * @return
     */
    @GetMapping("/c2")
    public Result cookie2(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies) {
            log.info("【cookie】:{},{}", cookie.getName(), cookie.getValue());
        }
        return Result.success();
    }
}
  1. 从请求中拿到cookie信息
Cookie[] cookies = req.getCookies();    // 获得cookie
cookie.getName()						// 获得cookie中的key
cookie.getValue()						// 获得cookie中的value
  1. 服务器响应给客户端cookie
new Cookie("lastLoginTime", System.currentTimeMillis() + "");		//新建一个cookie
cookie.setMaxAge(24*60*60);				// 设置cookie的有效期
resp.addCookie(cookie);					// 响应给客户端一个cookie

Cookie一般保存在本地的用户目录下Appdate;

一个网站Cookie是否存在上限?

  • 一个Cookie只能保存一个信息;
  • 一个web站点(服务器)可以给浏览器发送多个cookie,最多存放20个cookie
  • Cookie大小有限制4kb;
  • 浏览器上限300个cookie

删除cookie

  • 不设置有效期,关闭浏览器,自动失效
  • 有效期设置为0;

编码解码:

// URL编码
String name = URLEncoder.encode("小焦同学", "UTF-8");
Cookie cookie = new Cookie("name", name);

// URL解码
String name = URLDecoder.decode(cookie.getValue(), "UTF-8");
out.write(name);

在这里插入图片描述

3. Session

  • 服务器技术,利用这个技术,可以保存用户的会话信息?我们可以把信息或者数据放到session中

常见:网站登录之后,下次不用登录了,第二次访问直接上去

在这里插入图片描述
流程

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

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

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

在这里插入图片描述

什么是Session:

  • 服务器会给每一个用户(浏览器)创建一个Session对象;
  • 一个Session独占一个浏览器,只要浏览器没有关闭,这个Session就存在;
  • 用户登录之后,整个网站它都可以访问!–>保存用户信息;保存购物车信息…
// session创建的时候做了哪些事情(将value存放到cookie中)
// Cookie cookie = new Cookie("JSESSIONID", sessionId);
// resp.addCookie(cookie);

使用场景:

  • 保存一个登陆用户信息;
  • 购物车信息;
  • 在整个网站中经常使用的数据,我们将它保存在session中;

使用session:

@Slf4j
@RestController
public class SessionController {
    @GetMapping("/s1")
    public Result session1(HttpSession session,HttpServletRequest request) {
        HttpSession session1 = request.getSession(true);
        log.info("HttpSession-s1: {}", session.hashCode());
        // 往session中存储数据
        // session 就是一个大的HashMap
        session.setAttribute("loginUser", "tom");
        session.setAttribute("name","小焦同学");
        Person person = new Person("小焦",15);

        session.setAttribute("person", person);
        // 获取Session的ID
        String sessionId = session.getId();

        // 判断Session是不是新创建
        if (session.isNew()) {
            resp.getWriter().write("session创建成功ID:"+sessionId);
        }else
            resp.getWriter().write("session已经在服务器中存在了ID:"+sessionId);

        // session创建的时候做了哪些事情(将value存放到cookie中)
//        Cookie cookie = new Cookie("JSESSIONID", sessionId);
//        resp.addCookie(cookie);
        return Result.success();
    }

    @GetMapping("/s2")
    public Result session2(HttpServletRequest request) {
        HttpSession session = request.getSession();
        // session.hashCode():每个对象都有唯一标识,如果两个对象的hashCode相同,那么两个session就是同一个
        log.info("HttpSession-s2: {},{}", session.hashCode(), session.getId());
        // 从session中获取数据
        Object loginUser = session.getAttribute("loginUser");
        log.info("loginUser: {}", loginUser);
        return Result.success(loginUser);
    }
    
    @GetMapping("/s2")
    public Result session2(HttpSession session) {
        // session.hashCode():每个对象都有唯一标识,如果两个对象的hashCode相同,那么两个session就是同一个
        log.info("HttpSession-s2: {},{}", session.hashCode(), session.getId());
        // 从session中获取数据
        Object loginUser = session.getAttribute("loginUser");
        log.info("loginUser: {}", loginUser);
        return Result.success(loginUser);
    }
    
    // 移除session数据
    session.removeAttribute("name");
    // 手动注销session,再重新访问服务器会重新生成一个新的sessionId
    session.invalidate();
    
}

session销毁:

  • 手动销毁:session.invalidate()
  • 自动销毁:web.xml

会话自动过期:web.xml:

<!--设置session默认的失效时间-->
<session-config>
    <!--1440分钟后session自动失效,session以分钟为单位-->
    <session-timeout>1440</session-timeout>
</session-config>

注意:session名字可能会改变,如果改变就要改n个servlet类,这时使用常量,在servlet中直接使用类名.常量名

public final static String USER_SESSION = "USER_SESSION";
req.getSession().setAttribute(Constant.USER_SESSION,username);

四种区别:

pageContext.setAttribute("name1", "小焦01");		// 保存的数据只在一个页面中有效。
request.setAttribute("name2", "小焦02");			// 保存的数据只有在一个请求中有效,请求转发(跳转)会携带这个数据。
session.setAttribute("name3", "小焦03");			// 保存的数据只有在一次会话(从打开浏览器到关闭浏览器)中有效。
application.setAttribute("name4", "小焦04");		// 保存的数据在服务器中有效,从打开服务器到关闭服务器。
@GetMapping("/s1")
public Result session1(HttpSession session,HttpServletRequest request) {
    HttpSession session1 = request.getSession(true);
    log.info("HttpSession-s1: {}", session.hashCode());
    // 往session中存储数据
    // session 就是一个大的HashMap
    session.setAttribute("loginUser", "tom");
    return Result.success();
}

@GetMapping("/s2")
public Result session2(HttpServletRequest request) {
    HttpSession session = request.getSession();
    // session.hashCode():每个对象都有唯一标识,如果两个对象的hashCode相同,那么两个session就是同一个
    log.info("HttpSession-s2: {},{}", session.hashCode(), session.getId());
    // 从session中获取数据
    Object loginUser = session.getAttribute("loginUser");
    log.info("loginUser: {}", loginUser);
    return Result.success(loginUser);
}

优缺点

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

Session 底层是基于Cookie实现的会话跟踪,如果Cookie不可用,则该方案,也就失效了。
服务器集群环境为何无法使用Session?
在这里插入图片描述

  • 首先第一点,我们现在所开发的项目,一般都不会只部署在一台服务器上,因为一台服务器会存在一个很大的问题,就是单点故障。所谓单点故障,指的就是一旦这台服务器挂了,整个应用都没法访问了。

在这里插入图片描述

  • 所以在现在的企业项目开发当中,最终部署的时候都是以集群的形式来进行部署,也就是同一个项目它会部署多份。比如这个项目我们现在就部署了 3 份。

  • 而用户在访问的时候,到底访问这三台其中的哪一台?其实用户在访问的时候,他会访问一台前置的服务器,我们叫负载均衡服务器,我们在后面项目当中会详细讲解。目前大家先有一个印象负载均衡服务器,它的作用就是将前端发起的请求均匀的分发给后面的这三台服务器。

在这里插入图片描述

  • 此时假如我们通过 session 来进行会话跟踪,可能就会存在这样一个问题。用户打开浏览器要进行登录操作,此时会发起登录请求。登录请求到达负载均衡服务器,将这个请求转给了第一台 Tomcat 服务器。

    Tomcat 服务器接收到请求之后,要获取到会话对象session。获取到会话对象 session 之后,要给浏览器响应数据,最终在给浏览器响应数据的时候,就会携带这么一个 cookie 的名字,就是 JSESSIONID ,下一次再请求的时候,是不是又会将 Cookie 携带到服务端?

    好。此时假如又执行了一次查询操作,要查询部门的数据。这次请求到达负载均衡服务器之后,负载均衡服务器将这次请求转给了第二台 Tomcat 服务器,此时他就要到第二台 Tomcat 服务器当中。根据JSESSIONID 也就是对应的 session 的 ID 值,要找对应的 session 会话对象。

    我想请问在第二台服务器当中有没有这个ID的会话对象 Session, 是没有的。此时是不是就出现问题了?我同一个浏览器发起了 2 次请求,结果获取到的不是同一个会话对象,这就是Session这种会话跟踪方案它的缺点,在服务器集群环境下无法直接使用Session。

Session和Cookie的区别:

  • Cookie是把用户的数据写给用户的浏览器,浏览器保存(可以保存多个)
  • Session把用户的数据写到用户独占Session中,服务器端保存(保存重要信息,减少服务器资源的浪费)
  • Session对象由服务器创建
  • session是在服务器中存放数据(每个用户的sessionID都是唯一的)、cookie数据存放在客户端中

4. ApplicationContext

servletContext是所有用户共用一个,session是每个用户一个sessionID
在这里插入图片描述

2、JWT令牌

官网:https://jwt.io/

  • JSON Web Token是目前最流行的跨域认证解决方案,,适合前后端分离项目通过Restful API进行数据交互时进行身份认证
  • 它将用户信息加密到token里,服务器不保存任何用户信息,服务器通过使用保存的秘钥验证token的正确性,只要正确即通过验证
  • JWT令牌就是一个字符串

(前端)发送,前端发送请求到后端,放到请求头上面
对数据 进行编码(可以反编码)

在这里插入图片描述

JWT的组成:(JWT令牌由三个部分组成,三个部分之间使用英文的点来分割)

  • 第一部分:Header(头), 记录令牌类型、签名算法等。 例如:{“alg”:“HS256”,“type”:“JWT”}
  • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。 例如:{“id”:“1”,“username”:“Tom”}
  • 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。

签名的目的就是为了防jwt令牌被篡改,而正是因为jwt令牌最后一个部分数字签名的存在,所以整个jwt 令牌是非常安全可靠的。一旦jwt令牌当中任何一个部分、任何一个字符被篡改了,整个令牌在校验的时候都会失败,所以它是非常安全可靠的。

JWT是如何将原始的JSON格式数据,转变为字符串的呢?

  • 其实在生成JWT令牌时,会对JSON格式的数据进行一次编码:进行base64编码

  • Base64:是一种基于64个可打印的字符来表示二进制数据的编码方式。既然能编码,那也就意味着也能解码。所使用的64个字符分别是A到Z、a到z、 0- 9,一个加号,一个斜杠,加起来就是64个字符。任何数据经过base64编码之后,最终就会通过这64个字符来表示。当然还有一个符号,那就是等号。等号它是一个补位的符号

  • 需要注意的是Base64是编码方式,而不是加密方式。

1、生成和校验

  • 导入依赖
<!-- JWT依赖-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
  • JWT工具类
public class JwtUtils {
    
    /**
     * 生成JWT令牌
     * @param claims JWT第二部分负载 payload 中存储的内容
     * @return
     */
    public static String generateJwt(Map<String, Object> claims) {
        String jwt = Jwts.builder()
                .addClaims(claims)		// 自定义信息(有效载荷)
                .signWith(SignatureAlgorithm.HS256, "xiaokoen")		// 签名算法(头部)
                .setExpiration(new Date(System.currentTimeMillis() + 1800000L))		// 过期时间
                .compact();
        return jwt;
    }

    /**
     * 解析JWT令牌
     * @param jwt JWT令牌
     * @return JWT第二部分负载 payload 中存储的内容
     */
    public static Claims parseJWT(String jwt) {
        Claims claims = Jwts.parser()
                .setSigningKey("xiaokoen")		// 指定签名密钥(必须保证和生成令牌时使用相同的签名密钥)  
                .parseClaimsJws(jwt)			// 指定令牌Token
                .getBody();
        return claims;
    }
}

在这里插入图片描述

  • 第一部分解析出来,看到JSON格式的原始数据,所使用的签名算法为HS256。

  • 第二个部分是我们自定义的数据,之前我们自定义的数据就是id,还有一个exp代表的是我们所设置的过期时间。

  • 由于前两个部分是base64编码,所以是可以直接解码出来。但最后一个部分并不是base64编码,是经过签名算法计算出来的,所以最后一个部分是不会解析的。

篡改令牌中的任何一个字符,在对令牌进行解析时都会报错,所以JWT令牌是非常安全可靠的。

JWT令牌过期后,令牌就失效了,解析的为非法令牌。

通过以上测试,我们在使用JWT令牌时需要注意:

  • JWT校验时使用的签名秘钥,必须和生成JWT令牌时使用的秘钥是配套的。

  • 如果JWT令牌解析校验时报错,则说明 JWT令牌被篡改 或 失效了,令牌非法。

2、登录下发令牌

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

JwtUtils工具类

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

LoginController控制器

@PostMapping("/login")
public Result login(@RequestBody Emp emp) {
    log.info("【准备登录】");
    Emp dbEmp = empService.login(emp);

    if (dbEmp != null) {
        HashMap<String, Object> emps = new HashMap();
        emps.put("username",emp.getUsername());
        emps.put("password",emp.getPassword());
        String token = JwtUtils.generateJwt(emps);
        return Result.success(token);
    }

    return Result.error("用户名或密码不正确");
}

在这里插入图片描述

登录请求完成后,可以看到JWT令牌已经响应给了前端,此时前端就会将JWT令牌存储在浏览器本地。

3、过滤器Filter

在这里插入图片描述

什么是Filter?

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

在这里插入图片描述

1、Filter拦截路径

在这里插入图片描述

补充:FastJson

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.83</version>
</dependency>
1、概述

fastjson.jar是阿里开发的一款专门用于Java开发的包,可以方便的实现json对象与JavaBean对象的转换,实现JavaBean对象与json字符串的转换,实现json对象与json字符串的转换。实现json的转换方法很多,最后的实现结果都是一样的。

2、fastjson三个主要的类

JSONObject代表json对象

  • JSONObject实现了Map接口,所以JSONObject底层操作是由Map实现的
  • JSONObject对应json对象,通过各种形式的get()方法可以获取json对象中的数据,也可以利用size(),isEmpty()等方法获取“键:值”对的个数和判断是否为空。其本质是通过实现Map接口并调用接口中的方法完成的

JSONArray代表json对象数组

  • 内部是有List接口中的方法来完成操作的

JSON代表JSONObject和JSONArray的转化

  • JSON类源码分析与使用
  • 主要实现json对象,json对象数组,javabean对象,json字符串之间的相互转化

2、登录校验

在这里插入图片描述

流程

在这里插入图片描述

@Slf4j
@WebFilter("/*")
public class LoginFilter implements Filter {

    public static final String URL = "/login";
    public static final String TOKEN = "token";

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("执行过滤器...");
        /**
         * 1. 获取请求路径
         * 2. 判断是否是登录请求
         * 3. 获取请求头token
         * 4. 判断是否有token
         * 5. 解析token
         * 6. 放行
         */
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // 获取请求路径
        StringBuffer requestUrl = request.getRequestURL();
        String url = requestUrl.toString();

        /**
         * 登录界面不可以拦截,如果都拦截了,登录都没法登录了
         */
        // 判断是否是登录请求
        if (url.contains(URL)){
            filterChain.doFilter(servletRequest,servletResponse);
            return;
        }
        // 获取请求头token
        String token = request.getHeader(TOKEN);
        // 如果没有token,则没有权限
        if (token == null){
            error(response);
            return;
        }

        try {
            // 解析token,如果成功放行,如果抛出异常,token可能过期或者被别人恶意修改,没有权限
            JwtUtils.parseJWT(token);
            filterChain.doFilter(servletRequest,servletResponse);
        }catch (Exception e){
            error(response);
            return;
        }

    }

    /**
     * 返回未登录结果(返回失败消息)
     * @param response
     * @throws IOException
     */
    private void error(HttpServletResponse response) throws IOException {
        Result result = Result.error("没有token权限,请重新登录");
        String error = JSONObject.toJSONString(result);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(error);
    
}

3、过滤器链

在这里插入图片描述

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

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

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

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

4、拦截器Interceptor

什么是拦截器?

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

拦截器的作用

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

在这里插入图片描述

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

1、Interceptor拦截路径

在这里插入图片描述

2、登录校验

流程

在这里插入图片描述

  • 创建拦截器
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {

    public static final String URL = "/login";
    public static final String TOKEN = "token";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 获取请求路径
        StringBuffer requestUrl = request.getRequestURL();
        String url = requestUrl.toString();

        /**
         * 登录界面不可以拦截,如果都拦截了,登录都没法登录了
         */
        // 判断是否是登录请求
        if (url.contains(URL)){
            return true;
        }
        // 获取请求头token
        String token = request.getHeader(TOKEN);
        // 如果没有token,则没有权限
        if (token == null){
            error(response);
            return false;
        }

        try {
            // 解析token,如果成功放行,如果抛出异常,token可能过期或者被别人恶意修改,没有权限
            JwtUtils.parseJWT(token);
            return true;
        }catch (Exception e){
            error(response);
            return false;
        }

    }

    /**
     * 返回未登录结果(返回失败消息)
     * @param response
     * @throws IOException
     */
    private void error(HttpServletResponse response) throws IOException {
        Result result = Result.error("没有token权限,请重新登录");
        String error = JSONObject.toJSONString(result);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(error);
    }

}
  • 创建WebMvcConfigurer配置类
@Configuration			// 让IOC容器知道这是一个配置类,IOC容器才会扫描它
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")						// 拦截的路径
                .excludePathPatterns("/login");				// 去除的路径
    }
}

5、过滤器拦截器执行流程

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_koen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值