企业级SpringBoot单体项目模板 —— 使用 AOP + JWT实现登陆鉴权

  • 😜           :是江迪呀
  • ✒️本文关键词SpringBoot企业级项目模板
  • ☀️每日   一言没学会走就学跑从来都不是问题,要问问自己是不是天才,如果不是,那就要一步步来

文章目录

  • 使用JWT实现登录鉴权的流程
  • 一、AOP
    • 1.1 AOP依赖:
    • 1.2 AOP实现代码:
  • 二、JWT
    • 2.1 JWT的工作流程
    • 2.2 依赖:
    • 2.3 JWT工具类代码:
  • 三、ThreadLocal
    • 3.1 用户上下文代码:
  • 四、测试
    • 4.1 登陆生成token
    • 4.2 请求业务代码

上回我们完成了代码管理,现在终于可以也一些功能性的代码了,今天我聊一下如何使用JWT+AOP+ThreadLocal实现一个在企业中比较常用的登陆鉴权的功能。

使用JWT实现登录鉴权的流程

在这里插入图片描述

一、AOP

使用AOP拦截业务接口,来做鉴权,并将用户信息放入到ThreadLocal中去。

1.1 AOP依赖:

    <dependency>
      	 <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

1.2 AOP实现代码:

package com.shijiangdiya.config;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.shijiangdiya.common.UserContent;
import com.shijiangdiya.entity.user.User;
import com.shijiangdiya.utils.JWTUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class JWTAOP {

    @Around("execution(* com.shijiangdiya.controller.user.*Controller.*(..))")//拦截com.shijiangdiya.controller.user文件下面的所有以Controller结尾的接口里面的所有方法。
    public Object interceptController(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取HttpServletRequest
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 获取JWT token
        String token = request.getHeader("token");
        // 解密JWT token并获取用户信息
        DecodedJWT decodedJWT = JWTUtil.decodeToken(token);
        String userId =decodedJWT.getClaim("userId").asString();
        String userName = decodedJWT.getClaim("userName").asString();
        //将用户信息放入到ThreadLocal
        User user = new User();
        user.setId(Long.valueOf(userId));
        user.setName(userName);
        UserContent.setUserContext(user);
        try {
            // 继续处理请求
            return joinPoint.proceed();
        } finally {
            // 请求结束后移除ThreadLocal中的信息
            UserContent.removeUserContext();
        }
    }
}

这里一定要切记手动移除ThreadLocal中的值,原因是:

  • 如果你向ThreadLocal中存储了一个对象,这个对象将一直存在于ThreadLocal中,不会被垃圾回收。长时间运行的应用程序中,多次存储大量对象可能导致内存泄漏问题。
  • 某些情况下,存储在ThreadLocal中的数据可能包含敏感信息,如果不手动移除,这些信息可能被其他线程访问,导致数据泄露风险。\

这也是面试的一个问题点。

二、JWT

JWT全称为JsonWebToken,是一种无状态的,与传统的session相比它不会占用服务器的内存,在扩展安全跨平台方面要优于session。

2.1 JWT的工作流程

2.2 依赖:

   <!--引入jwt-->
   <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.4.0</version>
    </dependency>

2.3 JWT工具类代码:

package com.shijiangdiya.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.shijiangdiya.config.BusinessException;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;

public class JWTUtil {
    // 替换为自己的密钥
    private static final String SECRET_KEY = "shijiangdiya";

  /**
     * 生成token
     * @param map
     * @return
     */
    public static String generateToken(Map<String, String> map) {
        JWTCreator.Builder builder = JWT.create();
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.HOUR, 1);
        builder.withExpiresAt(instance.getTime());
        return builder.sign(Algorithm.HMAC256(SECRET_KEY));
    }
	/**
     * 解密token
     * @param token
     * @return
     */
    public static DecodedJWT decodeToken(String token) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
            JWTVerifier verifier = JWT.require(algorithm).build();
            return verifier.verify(token);
        } catch (Exception e) {
            throw new BusinessException("Token验证失败!");
        }
    }
}

三、ThreadLocal

ThreadLocal是一个线程本地变量,它允许每个线程都有自己独立的变量副本。ThreadLocal通常用于在多线程环境中存储和访问线程相关的数据,以避免线程间的数据竞争和并发问题。

一般在企业级中都会使用一个UserInfo的基础类,然后需要使用到当前登录用户信息的类要继承UserInfo,这样做代码耦合度太高。而使用Threadlocal存放用户信息,就可以避免这样的情况,不需要继承可以随处可以使用,并且线程之间相互隔离,比较方便。

3.1 用户上下文代码:

package com.shijiangdiya.common;
import com.shijiangdiya.entity.user.User;
public class UserContent {
    private static final ThreadLocal<User> userInfo = new ThreadLocal();

    /**
     * 获取用户信息
     * @return
     */
    public static User getUserContext(){
        return userInfo.get();
    }

    /**
     * 设置用户信息
     * @return
     */
    public static void setUserContext(User userContext){
        userInfo.set(userContext);
    }

    /**
     * 清除用户信息
     * @return
     */
    public static void removeUserContext(){
        userInfo.remove();
    }
}

四、测试

4.1 登陆生成token

切记登陆的接口要绕过AOP的拦截。

package com.shijiangdiya.controller.login;

import com.shijiangdiya.config.AbstractController;
import com.shijiangdiya.config.Response;
import com.shijiangdiya.entity.user.User;
import com.shijiangdiya.model.user.LoginUserQO;
import com.shijiangdiya.service.user.IUserService;
import com.shijiangdiya.utils.JWTUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/user")
public class LoginController extends AbstractController {
    @Autowired
    private IUserService userService;

    @PostMapping("/login")
    public Response login(@RequestBody @Valid LoginUserQO qo){
    	//查询数据的人员信息
        List<User> userInfoByName = userService.getUserByName(qo.getUserName());
        if(CollectionUtils.isEmpty(userInfoByName)){
            return new Response("401","用户不存在!",null);
        }
        List<User> collect = userInfoByName.stream().filter(user -> StringUtils.equals(user.getPassword(), qo.getPassword())).collect(Collectors.toList());
        if(CollectionUtils.isEmpty(collect)){
            return new Response("401","用户密码错误!",null);
        }
        //获取token
        Map<String,String> userInfo = new HashMap<>();
        userInfo.put("userId",String.valueOf(collect.get(0).getId()));
        userInfo.put("userName",collect.get(0).getName());
        String token = JWTUtil.generateToken(userInfo);
        return new Response("200","登陆成功!",token);
    }
}

在这里插入图片描述

4.2 请求业务代码

package com.shijiangdiya.controller.user;


import com.shijiangdiya.common.UserContent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/test1")
    public void test1(){
        Long id = UserContent.getUserContext().getId();
        System.out.println("用户id:"+id);
        String userName = UserContent.getUserContext().getName();
        System.out.println("用户名称:"+userName);

    }
}

在这里插入图片描述
控制台输出:

用户id:2
用户名称:hubayi

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

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

相关文章

C语言之动态内存管理实现通讯录(完整版)

我们在之前的博客中写过静态版的通讯录&#xff0c;我们今天来写一个动态版的&#xff0c;不需要规定它到底需要多大空间&#xff0c;只要还有内存&#xff0c;我们都可以存放的下&#xff01;同时&#xff0c;函数实现原理&#xff0c;我在通讯录静态版的博客里做了详细的讲解…

当Dubbo遇到高并发:探究流量控制解决方案

系列文章目录 面试Dubbo &#xff0c;却问我和Springcloud有什么区别&#xff1f; 超简单&#xff0c;手把手教你搭建Dubbo工程&#xff08;内附源码&#xff09; 【收藏向】从用法到源码&#xff0c;一篇文章让你精通Dubbo的SPI机制 Dubbo最核心功能——服务暴露的配置、使用…

VSCode 设置平滑光标

1.点击左下角的设置按钮&#xff0c;再点击设置 2.点击文本编辑器&#xff0c;点击光标&#xff0c;勾选控制是否启用平滑插入动画。 3.随便打开一个文件&#xff0c;上下左右移动光标时&#xff0c;会发现非常的流畅。 原创作者&#xff1a;吴小糖 创作时间&#xff1a;2023…

华为RS设备状态及接口配置命令

1、查看硬件信息 ①查看序列号 查看整机序列号 display esn display sn ②、查看功率 电源功率 display power 查看光模块功率 display transceiver interface gigabitethernet 1/0/0 verbose ③、查看风扇 display fan ④、查看温度 display temperature all ⑤、查看硬…

安全防御——二、ENSP防火墙实验学习

安全防御 一、防火墙接口以及模式配置1、untrust区域2、trust区域3、DMZ区域4、接口对演示 二、防火墙的策略1、定义与原理2、防火墙策略配置2.1 安全策略工作流程2.2 查询和创建会话 3、实验策略配置3.1 trust-to-untrust3.2 trust-to-dmz3.3 untrust-to-dmz 三、防火墙的区域…

ssh登录界面变成vim提示,进不去系统

是ubuntu系统 使用远程连接root&#xff0c;进去后发现界面变成vim编辑器的介绍界面了 使用普通用户登录 查询用户的登录shell是不是有问题 sudo vim /etc/passwd 发现用户shell变成了vim编辑器 修改为/bin/bash就可以正常登录了 重新登录测试就正常了

【Linux】 man命令使用

介绍 man命令是Linux下最核心的命令之一。而man命令也并不是英文单词“man”的意思&#xff0c;它是单词manual的缩写&#xff0c;即使用手册的意思。 man命令会列出一份完整的说明。 其内容包括命令语法、各选项的意义及相关命令 。更为强大的是&#xff0c;不仅可以查看Lin…

【计算机网络实验/wireshark】tcp建立和释放

wireshark开始捕获后&#xff0c;浏览器打开xg.swjtu.edu.cn&#xff0c;网页传输完成后&#xff0c;关闭浏览器&#xff0c;然后停止报文捕获。 若捕获不到dns报文&#xff0c;先运行ipconfig/flushdns命令清空dns缓存 DNS报文 设置了筛选条件&#xff1a;dns 查询报文目的…

openpnp - 74路西门子飞达控制板(主控板STM32_NUCLEO-144)实现

文章目录 openpnp - 74路西门子飞达控制板(主控板STM32_NUCLEO-144)实现概述飞达控制底板硬件电路程序的修改END openpnp - 74路西门子飞达控制板(主控板STM32_NUCLEO-144)实现 概述 现在调试自己的openpnp设备, 在收尾, 将飞达控制板弄好, 能正常控制设备飞达安装平台上装满…

go语言 | grpc原理介绍(二)

gRPC gRPC 是一个高性能、通用的开源 RPC 框架&#xff0c;其由 Google 2015 年主要面向移动应用开发并基于 HTTP/2 协议标准而设计&#xff0c;基于 ProtoBuf 序列化协议开发&#xff0c;且支持众多开发语言。 由于是开源框架&#xff0c;通信的双方可以进行二次开发&#x…

前端基础之CSS

目录 一、CSS介绍 CSS语法 CSS注释 CSS的几种引入方式 二、CSS选择器 基本选择器 组合选择器 属性选择器 分组和嵌套选择器 伪类选择器 伪元素选择器 选择器的优先级 三、CSS属性相关 宽和高 字体属性 文字属性 背景属性 边框 border-radius display属性 …

与AI对话的艺术:如何优化Prompt以获得更好的响应反馈

前言 在当今数字化时代&#xff0c;人工智能系统已经成为我们生活的一部分。我们可以在智能助手、聊天机器人、搜索引擎等各种场合与AI进行对话。然而&#xff0c;要获得有益的回应&#xff0c;我们需要学会与AI进行有效的沟通&#xff0c;这就涉及到如何编写好的Prompt。 与…

docker 安装 minio (单体架构)

文字归档&#xff1a;https://www.yuque.com/u27599042/coding_star/qcsmgom7basm6y64 查询 minio 镜像 docker search minio拉取镜像 docker pull minio/minio创建启动 minio 容器 用户名长度至少为 3&#xff0c;密码长度至少为 8 docker run \ -p 9000:9000 \ -p 9090:909…

轻量封装WebGPU渲染系统示例<13>- 屏幕空间后处理效果(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/main/src/voxgpu/sample/ScreenPostEffect.ts 此示例渲染系统实现的特性: 1. 用户态与系统态隔离。 细节请见&#xff1a;引擎系统设计思路 - 用户态与系统态隔离-CSDN博客 2. 高频调用与低频调用隔离。…

C++基础——对于C语言缺点的补充(1)

目录 1.命名空间&#xff1a; 1.1 为什么要引入命名空间&#xff1a; 1.2 命名空间的作用&#xff1a; 1.3 如何访问命名空间内的变量&#xff1a; 1.4 命名空间的嵌套&#xff1a; 1.5 不同文件下同名命名空间的合并&#xff1a; 1.6 命名空间的展开&#xff1a; 2. C…

canal+es+kibana+springboot

1、环境准备 服务器&#xff1a;Centos7 Jdk版本&#xff1a;1.8 Mysql版本&#xff1a;5.7.44 Canal版本&#xff1a;1.17 Es版本&#xff1a;7.12.1 kibana版本&#xff1a;7.12.1 软件包下载地址&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1jRpCJP0-hr9aI…

openGauss学习笔记-114 openGauss 数据库管理-设置安全策略-设置帐号有效期

文章目录 openGauss学习笔记-114 openGauss 数据库管理-设置安全策略-设置帐号有效期114.1 注意事项114.2 操作步骤 openGauss学习笔记-114 openGauss 数据库管理-设置安全策略-设置帐号有效期 114.1 注意事项 创建新用户时&#xff0c;需要限制用户的操作期限&#xff08;有…

antv/g6元素之combo

介绍 在 G6 中&#xff0c;“Combo” 是一种特殊的元素&#xff0c;用于组合和展示多个节点元素的一种方式。它通常用于表示一个组或子图&#xff0c;将多个相关节点组织在一起&#xff0c;并在图形中以单一的形状显示。 属性 type&#xff1a;Combo 的类型&#xff0c;通常是…

【Redis】Redis常用命令-getsetkeysexistsexpirettltype

文章目录 读取文档注意事项set命令get命令全局/通用命令KEYSEXISTSDELEXPIRETTLTYPE 读取文档注意事项 官方文档链接&#xff1a;https://redis.io/ 注意&#xff1a;redis的命令不区分大小写 在redis文档给出的语法格式说明&#xff1a; []&#xff1a;相当于一个独立的单元&a…

面试—如何介绍项目中的多级缓存?

项目中使用的多级缓存也就是 分布式缓存 Redis 本地缓存 Caffeine&#xff0c;那么令 Caffeine 作为一级缓存&#xff0c;Redis 作为二级缓存&#xff0c;在项目中通过记录数据的访问次数&#xff0c;将热点数据放在 本地缓存&#xff0c;将非热点数据放在 Redis缓存 中&#…