【Spring Boot统一功能处理】用户登录权限校验与拦截器,拦截器与传统的校验方式想比有什么好处呢? ? ?我们一起去探索其中的奥秘吧! ! !

news2024/10/7 13:20:32

前言:
大家好,我是良辰丫,今天我们要学习Spring Boot统一功能处理,什么叫统一功能呢?我们在javaEE初阶学习过前后端交互,约定交互时的统一格式,其中这种约定就是一个统一功能.💌💌💌

🧑个人主页:良辰针不戳
📖所属专栏:javaEE进阶篇之框架学习
🍎励志语句:生活也许会让我们遍体鳞伤,但最终这些伤口会成为我们一辈子的财富。
💦期待大家三连,关注,点赞,收藏。
💌作者能力有限,可能也会出错,欢迎大家指正。
💞愿与君为伴,共探Java汪洋大海。

在这里插入图片描述

目录

  • 1. 用户的登录权限校验
    • 1.1 传统的登录校验方式
    • 1.2 Spring AOP ⽤户统⼀登录验证的问题
    • 1.3 Spring 拦截器
      • 1.3.1 熟悉多级路由
      • 1.3.2 创建自定义拦截器
      • 1.3.3 拦截器加到系统配置
      • 1.3.4 测试拦截器
  • 2. 拦截器的实现原理
    • 2.1 图解分析
    • 2.2 源码分析
    • 2.3 添加前缀
      • 2.3.1 系统的配置文中设置
      • 2.3.2 在 application.properies中设置

1. 用户的登录权限校验

⽤户登录权限的发展从之前每个⽅法中⾃⼰验证⽤户登录权限,到现在统⼀的⽤户登录验证处理,它是⼀个逐渐完善和逐渐优化的过程。

1.1 传统的登录校验方式

package com.example.demo;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@RestController
@RequestMapping("/get")
public class GetSession {
    @RequestMapping("/login")
    public String login(HttpServletRequest request) {
        // 切记一定要加 false,这表示有 session 就获取,没有不会创建
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("ylc") != null) {
            return "登录成功";
        } else {
            return "登录失败";
        }
    }
}

在这里插入图片描述

  • 当我们建立一个会话信息的时候,它就会登录成功,我们在前面的文章有如何建立会话信息,在这里我就不再写了.

  • 从上述代码可以看出,每个⽅法中都有相同的⽤户登录验证权限,它的缺点是:

  1. 每个⽅法中都要单独写⽤户登录验证的⽅法,即使封装成公共⽅法,也⼀样要传参调⽤和在⽅法中
    进⾏判断。
  2. 添加控制器越多,调⽤⽤户登录验证的⽅法也越多,这样就增加了后期的修改成本和维护成本。
  3. 这些⽤户登录验证的⽅法和接下来要实现的业务⼏何没有任何关联,但每个⽅法中都要写⼀遍。
    所以提供⼀个公共的 AOP ⽅法来进⾏统⼀的⽤户登录权限验证迫在眉睫。

1.2 Spring AOP ⽤户统⼀登录验证的问题

说到统⼀的⽤户登录验证,我们想到的第⼀个实现⽅案是 Spring AOP 前置通知或环绕通知来实现,具体实现代码如下:

选择和springboot版本相同的AOP框架依赖,springboot版本为2.7.13,那么AOP的框架也为2.7.13

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>2.7.13</version>
</dependency>

在上一篇AOP的文章中我们学习了AOP的拦截方式

package com.example.demo;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
//该注解表示这个类是一个切面
@Aspect
//该注解表示该类随着项目的启动而启动
@Component
public class UserAspect {
    //定义切点,配置拦截规则
    @Pointcut("execution(* com.example.demo.controller.*.*(..))")
    public void pointcut() {

    }
    @Before("pointcut()")
    public void beforeAdvice() {

        System.out.println("我是前置通知");
    }
    @After("pointcut()")
    public void afterAdvice() {

        System.out.println("我是后置通知");
    }
    // 环绕⽅法
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint){
        Object obj = null;
        System.out.println("Around ⽅法开始执⾏");
        try {
            // 执⾏拦截⽅法
            obj = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("Around ⽅法结束执⾏");
        return obj;
    }

}

如果要在以上 Spring AOP 的切⾯中实现⽤户登录权限效验的功能,有以下两个问题:

  1. 在切面类中很难获取到 HttpSession 对象。
  2. 我们要对⼀部分⽅法进⾏拦截,⽽另⼀部分⽅法不拦截,这样的话排除⽅法的规则很难定义,甚⾄没办法定义.也就是定义拦截的规则(表达式)非常难.

于是乎,我们引入了拦截器 ! ! !

1.3 Spring 拦截器

对于以上问题 Spring 中提供了具体的实现拦截器:HandlerInterceptor,拦截器的实现分为以下两个步骤:

  1. 创建⾃定义拦截器,实现 HandlerInterceptor 接⼝的 preHandle(执⾏具体⽅法之前的预处理)⽅法。
  2. 将⾃定义拦截器加⼊ WebMvcConfigurer 的 addInterceptors ⽅法中。

1.3.1 熟悉多级路由

  • 这个路由指的是我们的网站地址(多级地址),不是我们家里联网的路由器哈.
  • java框架接近尾声,可能大家还是对路由不太熟悉,今天我们就来稍微讲一下.
package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class User {
    @GetMapping
    public String get(){
        return "这是get请求";
    }
    @PostMapping
    public String post(){
        return "这是post请求";
    }
}

在这里插入图片描述

接下来我们使用postman构造一个post请求观察结果.

在这里插入图片描述

  • 我们会发现get与post的注解需要一级路由(只需要在类上加路由)就可以进行访问;但是一个类中有多个get(或者多个post),不加二级路由就不能正常访问了.
  • 这种一级路由的方式很少用.

1.3.2 创建自定义拦截器

package com.example.demo;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LoginInter implements HandlerInterceptor {
    /**
     * 该方法返回一个布尔类型的值
     * 如果为真登录成功(验证通过),继续执行后序流程
     * 反之登录失败,后面的流程将不再执行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //false表示有会话信息得到会话信息
        //没有会话信息也不需要创建会话信息
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute("user") != null){
            //说明用户登录成功
            return true;
        }
        //否则登录失败
        //登录失败的情况可以做很多事情
        //比如跳转到登录页面或者返回一个401/403没有权限的状态码
        //具体怎么做取决于产品需求
        //下面表示重定向,跳转到登录页面
//        response.sendRedirect("/login.html");
        response.setStatus(401);
        return false;

    }
}

1.3.3 拦截器加到系统配置

package com.example.demo;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class Config implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInter())
                //设置拦截规则
                .addPathPatterns("/**")//拦截所有请求
                .excludePathPatterns("/**/user/login")//排除不拦截的请求
                .excludePathPatterns("/**/*.html");
    }
}

1.3.4 测试拦截器

  1. 首先创一个User类
package com.example.demo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class User {
    @RequestMapping("/login")
    public String login(){
        return "执行了login";
    }
    @RequestMapping("/get")
    public String get(){
        return "执行了get";
    }
}
  1. 通过网页进行测试

在这里插入图片描述

在这里插入图片描述

默认为全部拦截

addPathPatterns:表示需要拦截的 URL,“**”表示拦截任意⽅法(也就是所有⽅法)。
excludePathPatterns:表示需要排除的 URL。
说明:以上拦截规则可以拦截此项⽬中的使⽤ URL,包括静态⽂件(图⽚⽂件、JS 和 CSS 等⽂件)

2. 拦截器的实现原理

2.1 图解分析

  1. 没有拦截器的时候

在这里插入图片描述

  1. 加入拦截器后

在这里插入图片描述

2.2 源码分析

我们就运行我们上面的拦截器代码,然后通过浏览器访问观察控制台.

在这里插入图片描述

在这里插入图片描述

  • 我们可以从控制台得到的信息是所有的 Controller 执⾏都会通过⼀个调度器 DispatcherServlet 来实现.
  • 所有⽅法都会执⾏ DispatcherServlet 中的 doDispatch 调度⽅法

接下来我们来看一个doDispatch的源码.

在这里插入图片描述

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

        try {
            try {
                ModelAndView mv = null;
                Object dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    String method = request.getMethod();
                    boolean isGet = HttpMethod.GET.matches(method);
                    if (isGet || HttpMethod.HEAD.matches(method)) {
                        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                        if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                            return;
                        }
                    }
					//调用预处理
					//咱们在写拦截器的时候重写的方法就是PreHandle
					//这是拦截器原理,如果不是真的话,直接拦截退出
					//不会执行下面的东西
                    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                        return;
                    }
                //执行controller的业务
                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }

                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }

从上述源码可以看出在开始执⾏ Controller 之前,会先调⽤ 预处理⽅法 applyPreHandle,⽽
applyPreHandle ⽅法的实现源码如下:

   boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        for(int i = 0; i < this.interceptorList.size(); this.interceptorIndex = i++) {
            HandlerInterceptor interceptor = (HandlerInterceptor)this.interceptorList.get(i);
            if (!interceptor.preHandle(request, response, this.handler)) {
                this.triggerAfterCompletion(request, response, (Exception)null);
                return false;
            }
        }

        return true;
    }
  • 上面的代码中运用了循环,它会把你所有的拦截规则循环一次,判断哪些该拦截,哪些不该拦截,每次循环拿一个拦截器.
  • 在我们的项目中可以有多个拦截器,也就是通过 registry再添加一个拦截规则.
//一个类中可以配置多个拦截器
registry.addInterceptor(new LoginInter()).addPathPatterns();
registry.addInterceptor(new LoginInter()).addPathPatterns();
registry.addInterceptor(new LoginInter()).addPathPatterns();
  • 通过上⾯的源码分析,我们可以看出,Spring 中的拦截器也是通过动态代理和环绕通知的思想实现的.
  • 用户在调用目标方法的时候首先要通过代理对象,代理对象帮助我们去调用.

在这里插入图片描述

2.3 添加前缀

2.3.1 系统的配置文中设置

package com.example.demo;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class Config implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInter())
                //设置拦截规则
//                .addPathPatterns("/**")//拦截所有请求
                .excludePathPatterns("/**/user/login")//排除不拦截的请求
                .excludePathPatterns("/**/user/get")
                .excludePathPatterns("/**/*.html");


    }
    //所有的接口添加前缀信息
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
    //c->true是固定语法
    //所有的地址都添加这个前缀
        configurer.addPathPrefix("liangchen",c->true);
    }
}

此时我们再按照之前的访问方式就不能正常访问了.

在这里插入图片描述

接下来我们再地址中添加前缀可以正常访问

在这里插入图片描述

2.3.2 在 application.properies中设置

注意我们在设置之前把刚才的第一种方式设置的前缀进行注释,防止干扰我们的结果.

在这里插入图片描述

接下来在我们的application.properies中设置

在这里插入图片描述

server.servlet.context-path=/liangchen

接下来观察我们的浏览器访问路径,还是有laingchen的前缀.

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/704156.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VS Code报错 No module named ‘torch‘ (但已经安装了pytorch)

一、复现错误程序 创建一个python文件 test.py&#xff0c;其内容为&#xff1a; import torch print(torch.__version__)使用VS Code打开并运行该程序时&#xff0c;会出现以下错误&#xff1a; ModuleNotFoundError: No module named ‘torch’ 二、解决方案 选择适合的Pyt…

Python测试应用与工具

文章目录 前言环境准备unittestpytestpytest插件 mock最后 前言 例如&#xff1a;随着人工智能的不断发展&#xff0c;机器学习这门技术也越来越重要&#xff0c;很多人都开启了学习机器学习&#xff0c;本文就介绍了机器学习的基础内容。 Python测试应用与公具 今天跟大家分享…

MVTEC 3D dataset

官网&#xff1a;https://www.mvtec.com/company/research/datasets/mvtec-3d-ad/downloads https://www.mvtec.com/company/research/datasets/mvtec-3d-adhttps://www.mvtec.com/company/research/datasets/mvtec-3d-ad 数据大小&#xff1a;13个G 1. 介绍 MVTec 3D异常检测…

【lora模块调试:亿百特lora-型号E22-400T30D-V=初步调试踩坑-认识模块-了解协议(1)】

【lora模块调试&#xff1a;亿百特lora-型号E22-400T30D-V初步调试踩坑-认识模块-了解协议&#xff08;1&#xff09;】 1、概述2、实验环境3-1&#xff1a;先行了解3-2&#xff1a;经验总结4、硬件线路连接方式1、厂家提供的ttl转usb的模块2、使用开发板上的串口3、自己弄个转…

基于Java+Vue前后端分离网上拍卖系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

智能批量剪辑系统源码开发者如何减少服务器成本?

一、智能混剪批量剪辑自研与接入第三方“如阿里云”接口的差别 智能混剪批量剪辑自研和接入第三方"如阿里云"接口的差别主要在于技术实现和功能定制。自研混剪系统需要团队投入大量时间和资源来研发和维护&#xff0c;并且能够根据用户需求定制和优化功能&#xff0…

6 中断概览(STM32HAL库)

目录 中断概览 STM32异常和中断介绍 STM32的异常一览 STM32的中断表一览 中断的优先级 中断的优先级分组 优先级分组 嵌套向量中断控制器(NVIC)功能 中断概览 什么是中断&#xff1f; 中断是指计算机运行过程中&#xff0c;出现某些意外情况需主机干预时&#xff0c;机器…

如何理解Spring Bean?

文章目录 一、什么是 Spring Bean&#xff1f;二、定义Spring Bean 有哪些方式&#xff1f;三、Spring 容器是如何加载 Bean 的&#xff1f; 我一共分三段来介绍&#xff0c;首先&#xff0c;介绍什么是 Spring Bean&#xff1f;然后&#xff0c;定义Spring Bean 有哪些方式&am…

typescript Constructor Set requires ‘new‘

使用typescript的class继承时报错 “构造函数集需要’new’” ts代码 class MySet extends Set {constructor() {super();}let myset new MySet();控制台错误 只需要在tsconfig.json文件中添加以下配置即可 "compilerOptions": {"target": "es6…

面试常问 什么是回表?为什么需要回表?

小伙伴们在面试的时候&#xff0c;有一个特别常见的问题&#xff0c;那就是数据库的回表。什么是回表&#xff1f;为什么需要回表&#xff1f; 索引结构 要搞明白这个问题&#xff0c;需要大家首先明白 MySQL 中索引存储的数据结构。这个其实很多小伙伴可能也都听说过&#xf…

SQL方言:传统关系型数据库下的方言对比

前言&#xff1a; 技术多元化是一个趋势&#xff0c;多语言并存&#xff0c;多数据库适配&#xff0c;多环境兼容>< 场景&#xff1a; 当从SQL Server数据库迁移到MySql数据库或者Oracle数据库&#xff0c;甚至国产化数据库&#xff0c;不同数据库之间可以自定义切换&…

实现firebase FCM和Analytics

前提&#xff1a;1.需要vpn 2.带有google 服务的手机 注意&#xff01;&#xff01;&#xff01; 这个在2023年6月30日时还是测试版&#xff0c;所以手机有概率接收不到消息 编写代码前需要在https://console.firebase.google.com/ 配置好参数 这里的token值需要填写代码内的i…

macOS 系统 安装 Kafka 快速入门

博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客&#x1f466;&#x1f3fb; 《java 面试题大全》 &#x1f369;惟余辈才疏学浅&#xff0c;临摹之作或有不妥之处&#xff0c;还请读者海涵指正。☕&#x1f36d; 《MYSQL从入门到精通》数据库是开发者必会基础之…

神策(Android)- 集成基础埋点的整个过程

记得最早以前都是用友盟全家桶&#xff0c;埋点是用友盟&#xff0c;推送也是用友盟&#xff1b;但是近俩年我参与开发的app&#xff0c;埋点都是用神策、推送都是用极光私服&#xff0c;分享都是去对应集成对应平台的SDK 神策篇 神策&#xff08;Android&#xff09;- 集成基…

2023-6-30-第十二式组合模式

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f4a5;&#x1f4a5;&#x1f4a5;欢迎来到&#x1f91e;汤姆&#x1f91e;的csdn博文&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f49f;&#x1f49f;喜欢的朋友可以关注一下&#xf…

Linux的tmux的使用

0.前言 您好&#xff0c;这里是limou3434&#xff0c;本次我将给您带来Linux下tmux的使用。 如果您感兴趣也可以看看我的其他内容。 1.tmux的基础概念 tmux是一款在Linux里运行在终端上的软件&#xff0c;可以使得终端具有强大的多任务管理功能&#xff08;以下是在Ubuntu环…

CSS知识点汇总(八)--Flexbox

1. flexbox&#xff08;弹性盒布局模型&#xff09;是什么&#xff0c;适用什么场景&#xff1f; 1. flexbox&#xff08;弹性盒布局模型&#xff09;是什么 Flexible Box 简称 flex&#xff0c;意为”弹性布局”&#xff0c;可以简便、完整、响应式地实现各种页面布局。采用…

Python高级教程:简单爬虫实践案例

学习目标 能够知道Web开发流程 能够掌握FastAPI实现访问多个指定网页 知道通过requests模块爬取图片 知道通过requests模块爬取GDP数据 能够用pyecharts实现饼图 能够知道logging日志的使用 一、基于FastAPI之Web站点开发 FastAPI是一个高性能、易于使用、快速编写API的…

新品亮相丨美格智能高性能Cat.1 bis模组SLM332X上市

6月29日&#xff0c;2023 MWC 上海世界移动通信大会火热进行中。展会现场&#xff0c;美格智能正式发布基于芯翼信息科技XY4100芯片平台研发的高性能4G LTE Cat.1 bis模组SLM332X。该产品可广泛应用于智能支付、智慧表计、共享经济、公网对讲机、定位追踪、智能穿戴、安防监控等…

Web3本地搭建truffle智能合约开发环境

之前的几篇文章 我们是成功的操作了我们本地区块链的 那么 本文 我们就来说说智能合约 啊 不容易啊 扯了这么久 终于到这了 智能合约是部署在区块链上 不可逆的 一种去中心化的程序&#xff0c;他没有任何第三方公司来管理这个程序和数据 然后 还有就是怎么连接到区块链上的智能…