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

news2025/1/16 5:33:39
  • 😜           :是江迪呀
  • ✒️本文关键词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

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

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

相关文章

docker 常用

系统 Ubuntu 20.04 64位 安装文档 ubuntu&#xff1a;https://docs.docker.com/engine/install/ubuntu/ centos&#xff1a;https://docs.docker.com/engine/install/centos/ debian&#xff1a;https://docs.docker.com/engine/install/debian/ 常用命令 查看镜像 docke…

NeRF-SLAM部署运行(3060Ti)

记录在部署运行期间遇到的一些问题&#xff0c;分享给大家~ 一、环境 RTX 3060 Ti、8G显存、Ubuntu18.04 二、部署 1. 下载代码 git clone https://github.com/jrpowers/NeRF-SLAM.git --recurse-submodules git submodule update --init --recursive cd thirdparty/insta…

Nacos 的底层实现原理 注册中心的两种调用方式

目录 1. Nacos 的底层实现原理 1.1 配置中心自动刷新实现原理 1.2 注册中心底层实现原理 2. Nacos 注册中心的两种调用方式 2.1 RestTemplate Spring Cloud LoadBalancer 的调用方式 2.2 使用 OpenFeign Spring Cloud LoadBalancer 1. Nacos 的底层实现原理 1.1 配置中心…

Python测试之Pytest详解

概要 当涉及到python的测试框架时&#xff0c;pytest是一个功能强大且广泛应用的第三方库。它提供简洁而灵活的方式来编写和执行测试用例&#xff0c;并具有广泛的应用场景。下面是pytest的介绍和详细使用说明&#xff1a; pytest是一个用于python单元测试的框架&#xff0c;它…

node插件express(路由)的插件使用(二)——body-parser和ejs插件的基本使用

文章目录 前言一、express使用中间件body-parser获取请全体的数据1. 代码2. 效果 二、express使用ejs&#xff08;了解即可&#xff09;1.安装2.作用3.基本使用&#xff08;1&#xff09;代码&#xff08;2&#xff09;代码分析和效果 4.列表渲染&#xff08;1&#xff09;代码…

Canvas 实现进度条展示统计数据示例

canvas可以画柱状图&#xff0c;如下就是一个例子&#xff0c;主要用到了lineWidth&#xff0c;beginPath&#xff0c;lineCap等知识点。 效果图 源代码 <!DOCTYPE Html> <html> <head><title>Line Chart Demo</title><meta http-equiv&quo…

一个球从100m高度自由落下,每次落地后反弹回原高度的一半,再落下,再反弹。求它在第10次落地时共经过多少米,第10次反弹多高

一.思路分析 这是一个简单的物理题目&#xff0c;解题思路比较明确。程序使用 for 循环来模拟球的下落和反弹过程&#xff0c;通过多次计算得到最终结果&#xff0c;最后使用 printf 函数将结果输出。 定义初始高度 height 和总共经过的米数 distance 的变量&#xff0c;初始化…

【密评】商用密码应用安全性评估从业人员考核题库(十九)

商用密码应用安全性评估从业人员考核题库&#xff08;十九&#xff09; 国密局给的参考题库5000道只是基础题&#xff0c;后续更新完5000还会继续更其他高质量题库&#xff0c;持续学习&#xff0c;共同进步。 4501 判断题 依据《信息系统密码应用高风险判定指引》&#xff0c…

服务器感染了.locked勒索病毒,如何确保数据文件完整恢复?

引言&#xff1a; 网络安全威胁的不断演变使得恶意软件如.locked勒索病毒成为当今数字时代的一大挑战。.locked勒索病毒能够加密您的文件&#xff0c;然后要求支付赎金以解锁它们。本文将深入探讨.locked勒索病毒的特点&#xff0c;以及如何应对感染&#xff0c;以及预防这种类…

数据可视化:动态柱状图

终于来到最后一个数据可视化的文章拿啦~~~ 在这里学习如何绘制动态柱状图 我先整个活 (๑′ᴗ‵๑)&#xff29; Lᵒᵛᵉᵧₒᵤ❤ 什么是pyecharts&#xff1f; 答&#xff1a; Python的Pyecharts软件包。它是一个用于Python数据可视化和图表绘制的库&#xff0c;可用于制作…

安装 2023最新版本的Tableau Desktop 时出现“0x80070643”错误

安装失败的原因&#xff1a; “0x80070643”错误是Microsoft错误。 必需的安装组件无法启动&#xff0c;通常是C库&#xff0c;或者使用了无效的操作系统版本。 通过控制面板——程序与功能可以查看到自己电脑Microsoft Visual C的版本&#xff0c;像我的话是比较低的&…

第四章IDEA操作Maven

文章目录 创建父工程开启自动导入配置Maven信息创建Java模块工程创建 Web 模块工程 在IDEA中执行Maven命令直接执行手动输入 在IDEA中查看某个模块的依赖信息工程导入来自版本控制系统来自工程目录 模块导入情景重现导入 Java 类型模块 导入 Web 类型模块 创建父工程 开启自动导…

【GitHub】Watch、Star、Fork、Follow 有什么区别?

目录 一、前言二、区别1. Watch2. Star3. Fork4. Follow 一、前言 GitHub 是最受欢迎的代码托管平台之一&#xff0c;拥有大量的开源代码可供学习。 Github 中也有类似 “点赞”、“收藏”、“加关注” 的功能。 下面介绍下&#xff0c;GitHub 中 Watch、Star、Fork、Follow 有…

uni-app华为审核被拒,驳回原因:您的应用在运行时,未见向用户告知权限申请的目的

华为审核被拒&#xff1a; 您的应用在运行时&#xff0c;未见向用户告知权限申请的目的&#xff0c;向用户索取(相机存)等权限&#xff0c;不符合华为应用市场审核标准。 <uni-popup ref"perpopup" type"center" :mask-clickfalse><view class&qu…

幂等性(防重复提交)

文章目录 1. 实现原理2.使用示例3. Idempotent注解4. debug过程 主要用途&#xff1a;防止用户快速双击某个按钮&#xff0c;而前端没有禁用&#xff0c;导致发送两次重复请求。 1. 实现原理 幂等性要求参数相同的方法在一定时间内&#xff0c;只能执行一次。本质上是基于red…

Leetcode-509 斐波那契数列

使用循环 class Solution {public int fib(int n) {if(n 0){return 0;}if(n 1){return 1;}int res 0;int pre1 1;int pre2 0;for(int i 2; i < n; i){res pre1 pre2;pre2 pre1;pre1 res;}return res;} }使用HashMap class Solution {private Map<Integer,Int…

E-Office(泛微OA)前台任意文件读取漏洞复现

简介 泛微E-Office是一款企业级的全流程办公自动化软件&#xff0c;它包括协同办公、文档管理、知识管理、工作流管理等多个模块&#xff0c;涵盖了企业日常工作中的各个环节。在该产品前台登录页存在文件读取漏洞。 officeserver.php文件存在任意文件读取漏洞&#xff0c;通…

[100天算法】-有序矩阵中第K小的元素(day 58)

题目描述 给定一个 n x n 矩阵&#xff0c;其中每行和每列元素均按升序排序&#xff0c;找到矩阵中第 k 小的元素。 请注意&#xff0c;它是排序后的第 k 小元素&#xff0c;而不是第 k 个不同的元素。示例&#xff1a;matrix [[ 1, 5, 9],[10, 11, 13],[12, 13, 15] ], k …

如何实现多租户系统

shigen日更文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 不知道为什么&#xff0c;最近老是有一些失眠&#xff0c;凌晨睡&#xff0c;两点半还在醒着。脑子里想着自己生…

手动关闭PS中的TopazStudio2的登录窗口

2021 adobe photoshop Topaz Studio 2 不是使用防火墙出站规则&#xff0c;是手动关闭的解决方案 点击社区-切换用户&#xff0c;登录窗口会出现X&#xff0c;可以手动关闭