【wiki知识库】08.添加用户登录功能--后端SpringBoot部分

目录

一、今日目标?

二、SpringBoot后端实现

2.1 新增UserLoginParam

2.2 修改UserController

2.3 UserServiceImpl代码

2.4 创建用户上下文工具类

2.5?通过token校验用户(重要)

2.6 创建WebMvcConfig

2.7 用户权限校验拦截器


一、今日目标

上篇文章链接:【wiki知识库】08.添加用户登录功能–前端Vue部分修改-CSDN博客

这篇文章主要是实现一下用户登录功能的后端部分,登录功能需要使用redis,不懂redis可以看我之前的一篇文章。

Redis文章链接:【Spring】SpringBoot整合Redis,用Redis实现限流(附Redis解压包)_springboot 限流 redis-CSDN博客

那么为什么要用到Redis呢?

这个问题关系到整个系统的用户校验,当我们登录成功的时候,后端会生成一个用于用户校验的token值,然后把这个值传给前端,每次用户请求后端的时候都要带上这个token值,这个token的值当中记录了当前登录的用户是谁,还有过期时间等信息,这样子就可以防止那些没有登陆的用户去直接访问我们的后端调用接口。所以这个token还是需要妥善保管的,一旦token丢失别人就可能用你的token去发送请求,修改你的数据。

二、SpringBoot后端实现

2.1 新增UserLoginParam

这里也做了校验,其实这个事情完全可以放到前端实现,但是也要考虑到有直接调用接口的情况,这时也要给出错误提示。

@Data
public class UserLoginParam {

    @NotEmpty(message = "【用户名】不能为空")
    private String loginName;

    @NotEmpty(message = "【密码】不能为空")
    @Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,32}$", message = "【密码】规则不正确")
    private String password;

}

2.2 修改UserController

直接上代码吧。这里拿到了用户的账号和用户的密码,然后判断加密后的密码和数据库中取出来的用户密码是否相同,如果相同那么就可以登陆。登陆后通过工具类生成一个不会重复的Long类型的值作为该用户的token,然后以token为key,登录用户创建的对象作为值,保存到redis当中,以便于后续用户访问接口时,通过用户token来判断是哪个用户访问接口。

  @PostMapping("/login")
  public CommonResp login(@Valid @RequestBody UserLoginParam req) {
    req.setPassword(DigestUtils.md5DigestAsHex(req.getPassword().getBytes()));
    System.out.println(req);
    UserLoginVo userLoginResp = userService.login(req);
    Long token = snowFlake.nextId();
    userLoginResp.setToken(token.toString());
    redisTemplate.opsForValue().set(token.toString(), JSONObject.toJSONString(userLoginResp), 3600 * 24, TimeUnit.SECONDS);
    return new CommonResp(true,"登录成功",userLoginResp);
  }

  @GetMapping("/logout/{token}")
  public CommonResp logout(@PathVariable String token) {
    boolean res = redisTemplate.delete(token);
    String message = Boolean.TRUE.equals(res) ? "登出成功":"登出失败";
    return new CommonResp(true,message,null);
  }

2.3 UserServiceImpl代码

这个代码没有什么好说的,就是查找一次数据库进行账号密码的匹配。

public UserLoginVo login(UserLoginParam req) {
        User userDb = selectByLoginName(req.getLoginName());
        if (ObjectUtils.isEmpty(userDb)) {
            // 用户名不存在
            throw new RuntimeException("用户名不存在");
        } else {
            if (userDb.getPassword().equals(req.getPassword())) {
                // 登录成功
                UserLoginVo userLoginResp = CopyUtil.copy(userDb, UserLoginVo.class);
                return userLoginResp;
            } else {
                // 密码不对
                throw new RuntimeException("密码错误");
            }
        }
    }

2.4 创建用户上下文工具类

这个工具类用户用户登录后保存当前用户的上下文。

public class LoginUserContext implements Serializable {

    private static ThreadLocal<UserLoginVo> user = new ThreadLocal<>();

    public static UserLoginVo getUser() {
        return user.get();
    }

    public static void setUser(UserLoginVo user) {
        LoginUserContext.user.set(user);
    }

}

2.5通过token校验用户(重要)

校验用户token需要使用到拦截器或者过滤器,这里我使用拦截器进行用户token的校验。整体的校验流程如下

以下就是登录拦截器的代码,

/**
 * 拦截器:Spring框架特有的,常用于登录校验,权限校验,请求日志打印
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {
    private static final Logger LOG = LoggerFactory.getLogger(LoginInterceptor.class);
    @Resource
    private RedisTemplate redisTemplate;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // OPTIONS请求不做校验,
        // 前后端分离的架构, 前端会发一个OPTIONS请求先做预检, 对预检请求不做校验
        if (request.getMethod().toUpperCase().equals("OPTIONS")) {
            return true;
        }
        String path = request.getRequestURL().toString();
        LOG.info("接口登录拦截:,path:{}", path);

        //获取header的token参数
        String token = request.getHeader("token");
        LOG.info("登录校验开始,token:{}", token);
        if (token == null || token.isEmpty()) {
            LOG.info("token为空,请求被拦截");
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return false;
        }
        Object object = redisTemplate.opsForValue().get(token);
        // 证明redis中的用户信息过期了,需要重新登陆
        if (object == null) {
            LOG.warn("token无效,请求被拦截");
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return false;
        } else {
            LOG.info("已登录:{}", object);
            LoginUserContext.setUser(JSON.parseObject((String) object, UserLoginVo.class));
            return true;
        }
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    }
}

接下来要把这个拦截器注册到配置当中。


2.6 创建WebMvcConfig

在config包下创建该类。这个类当中配置了两个拦截器,一个是登录拦截器,另一个是用户权限校验拦截器。用户校验拦截器下边再说。登录拦截器只需要部分接口进行拦截就可以了,毕竟有的接口不需要登陆用户就可以访问。

有一点值得注意的是,在这个配置类中配置的拦截器的顺序会影响校验结果,校验的流程是根据你配置的拦截器的顺序从上往下校验的,如果你把拦截器配置写反了就会出错。

addInterceptor

注册一个拦截器

addPathPatterns

该拦截器需要拦截的路径

excludePathPatterns

该拦截器不需要拦截的路径

@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {

    @Resource
    LoginInterceptor loginInterceptor;
    @Resource
    ActionInterceptor actionInterceptor;

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns(
                        "/test/**",
                        "/redis/**",
                        "/user/login",
                        "/user/logout/**",
                        "/category/all",
                        "/ebook/list",
                        "/doc/all/**",
                        "/doc/find-content/**",
                );

        registry.addInterceptor(actionInterceptor)
                .addPathPatterns(
                        "/*/save",
                        "/*/delete/**",
                        "/*/reset-password");
    }
}

2.7 用户权限校验拦截器

看到下方的代码你应该知道了用户上下文的作用,通过用户上下文拿到用户的信息来判断该用户是否有访问该接口的权利,我们拒绝非admin用户外的用户进行增删改操作。

但是这种方法有点不太好不知道你们有没有感觉到,一旦用户多了之后,如果你想给用户分配权限,你就要添加很多的用户在这里。所以一种更好的方式就是RBAC权限校验,大家可以自己了解一下,也有更好的权限校验框架SpringSecurity,但是作为一个比较简单的项目,引入这个框架的学习成本就太大了。

/**
 * 拦截器:Spring框架特有的,常用于登录校验,权限校验,请求日志打印
 */
@Component
public class ActionInterceptor implements HandlerInterceptor {

    private static final Logger LOG = LoggerFactory.getLogger(ActionInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        // OPTIONS请求不做校验,
        // 前后端分离的架构, 前端会发一个OPTIONS请求先做预检, 对预检请求不做校验
        if ("OPTIONS".equals(request.getMethod().toUpperCase())) {
            return true;
        }
        UserLoginVo userLoginResp = LoginUserContext.getUser();
        if ("admin".equals(userLoginResp.getLoginName())) {
            // admin用户不拦截
            return true;
        }
        LOG.info("操作被拦截");
        response.setStatus(HttpStatus.OK.value());
        CommonResp commonResp = new CommonResp(false,"普通用户暂不开放增删改操作",null);
        response.setContentType("application/json;charset=UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().print(JSONObject.toJSON(commonResp));
        return false;
    }

}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分布式 IO 模块:开启药品罐装产线高效生产新纪元

在药品生产的精密领域&#xff0c;每一个环节都关乎着客户的健康与安全。药品罐装产线作为药品生产的关键环节&#xff0c;其高效运行与精准控制至关重要。明达技术MR30分布式 IO 模块&#xff0c;正以其卓越的性能&#xff0c;成为实现药品罐装产线高效控制&#xff0c;确保产…

【北京迅为】iTOP-4412全能版使用手册-第八十七章 安装Android Studio

iTOP-4412全能版采用四核Cortex-A9&#xff0c;主频为1.4GHz-1.6GHz&#xff0c;配备S5M8767 电源管理&#xff0c;集成USB HUB,选用高品质板对板连接器稳定可靠&#xff0c;大厂生产&#xff0c;做工精良。接口一应俱全&#xff0c;开发更简单,搭载全网通4G、支持WIFI、蓝牙、…

基于海思soc的智能产品开发(视频的后续开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们讨论了camera&#xff0c;也讨论了屏幕驱动&#xff0c;这些都是基础的部分。关键是&#xff0c;我们拿到了这些视频数据之后&#xff0c;…

Linux -- HTTP 请求 与 响应 报文

目录 请求报文&#xff1a; 请求方法 响应报文&#xff1a; 状态码 与 状态码描述 共性 常见的报头 请求报文&#xff1a; 请求方法 方法说明GET获取资源POST传输实体主体PUT传输文件HEAD获得报文首部DELETE删除文件OPTIONS询问支持的方法TRACE追踪路径CONNECT要求用…

HTML<img>标签

例子 如何插入图片&#xff1a; <img src"img_girl.jpg" alt"Girl in a jacket" width"500" height"600"> 下面有更多“自己尝试”的示例。 定义和用法 该<img>标签用于在 HTML 页面中嵌入图像。 从技术上讲&#x…

C++ 面向对象(继承)

三、继承 3.1 继承的概念 基于一个已有的类 去重新定义一个新的类&#xff0c;这种方式我们叫做继承 关于继承的称呼 一个类B 继承来自 类 A 我们一般称呼 A类&#xff1a;父类 基类 B类: 子类 派生类 B继承自A A 派生了B 示例图的语法 class vehicle // 车类 {}class …

mfc操作json示例

首先下载cJSON,加入项目; 构建工程,如果出现, fatal error C1010: unexpected end of file while looking for precompiled head 在cJSON.c文件的头部加入#include "stdafx.h"; 看情况,可能是加到.h或者是.cpp文件的头部,它如果有包含头文件, #include &…

基于微信小程序的模拟考试系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

Android10.0定制服务 APK安装或者更新过自动打开APK

需求描述 当用户APK安装|更新 成功之后自动打开APK。 实现思路 编写服务 注册接受安装广播 并判断安装的APK包名是否为客户APK包名,并自动打开。 具体实现 diff --git a/android/build/make/core/tasks/check_boot_jars/package_whitelist.txt b/android/build/make/cor…

linux 下tensorrt的yolov8的前向推理(python 版本)的实现

一、yolov8的python实现的环境搭建 #通过pip安装 pip install ultralytics #通过git克隆GitHub仓库 git clone <https://github.com/ultralytics/ultralytics.git> cd ultralytics #安装依赖 pip install -r requirements.txt #执行推理 yolo predict model./yolov8n.pt …

AI News(1/21/2025):OpenAI 安全疏忽:ChatGPT漏洞引发DDoS风险/OpenAI 代理工具即将发布

1、OpenAI 的安全疏忽&#xff1a;ChatGPT API 漏洞引发DDoS风险 德国安全研究员 Benjamin Flesch 发现了一个严重的安全漏洞&#xff1a;攻击者可以通过向 ChatGPT API 发送一个 HTTP 请求&#xff0c;利用 ChatGPT 的爬虫对目标网站发起 DDoS 攻击。该漏洞源于 OpenAI 在处理…

【数据挖掘实战】 房价预测

本次对kaggle中的入门级数据集&#xff0c;房价回归数据集进行数据挖掘&#xff0c;预测房屋价格。 本人主页&#xff1a;机器学习司猫白 机器学习专栏&#xff1a;机器学习实战 PyTorch入门专栏&#xff1a;PyTorch入门 深度学习实战&#xff1a;深度学习 ok&#xff0c;话不多…

ElasticSearch 学习课程入门(一)

引子 前文已经介绍了windows下如何安装ES&#xff0c;接下来的文章我会边学习边记录。OK&#xff0c;那就让我们开始吧。 一、ES基础操作 1、预备知识 &#xff08;1&#xff09;RESTful REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 …

QML Binding和Qt.binding详解

一、Binding详解 1、介绍 QML Binding是一种声明式的编程概念&#xff0c;它允许在Qt Quick应用中自动更新属性之间的关系。通过使用Binding&#xff0c;我们可以在属性之间建立依赖关系&#xff0c;当其中一个属性的值发生变化时&#xff0c;绑定的属性会自动更新。QML Bind…

【优选算法】3----快乐数

这是第三篇算法题&#xff0c;也是十分的不好想&#xff0c;虽然定位在简单&#xff0c;但我感觉还是有些难度的~ -------------------------------------begin----------------------------------------- 题目解析&#xff1a; 同样的&#xff0c;这道算法题&#xff0c;看题…

计算机网络 (52)秘钥分配

一、重要性 在计算机网络中&#xff0c;密钥分配是密钥管理中的一个核心问题。由于密码算法通常是公开的&#xff0c;因此网络的安全性主要依赖于密钥的安全保护。密钥分配的目的是确保密钥在传输过程中不被窃取或篡改&#xff0c;同时确保只有合法的用户才能获得密钥。 二、方…

Docker Load后存储的镜像及更改镜像存储目录的方法

Docker Load后存储的镜像及更改镜像存储目录的方法 Docker Load后存储的镜像更改镜像存储目录的方法脚本说明注意事项Docker作为一种开源的应用容器引擎,已经广泛应用于软件开发、测试和生产环境中。通过Docker,开发者可以将应用打包成镜像,轻松地进行分发和运行。而在某些场…

【三国游戏——贪心、排序】

题目 代码 #include <bits/stdc.h> using namespace std; using ll long long; const int N 1e510; int a[N], b[N], c[N]; int w[4][N]; int main() {int n;cin >> n;for(int i 1; i < n; i)cin >> a[i];for(int i 1; i < n; i)cin >> b[i…

unity——Preject3——摄像机动画

1.当预设体有改动时候记住要应用一下 2.创建摄像头动画步骤 3.动画相关设置 取消动作循环 4. Hax Exit Time 讲解 在Unity的Animator Controller中&#xff0c;"Has Exit Time" 是一个用于控制状态转换的选项。它决定了当前状态是否需要在转换到下一个状态之前完成…

从零开始:Gitee 仓库创建与 Git 配置指南

你好呀&#xff0c;欢迎来到 Dong雨 的技术小栈 &#x1f331; 在这里&#xff0c;我们一同探索代码的奥秘&#xff0c;感受技术的魅力 ✨。 &#x1f449; 我的小世界&#xff1a;Dong雨 &#x1f4cc; 分享我的学习旅程 &#x1f6e0;️ 提供贴心的实用工具 &#x1f4a1; 记…