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

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

文章目录

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

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

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

一、AOP

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

1.1 AOP依赖:

xml 复制代码
    <dependency>
      	 <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

1.2 AOP实现代码:

java 复制代码
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 依赖:

xml 复制代码
   <!--引入jwt-->
   <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.4.0</version>
    </dependency>

2.3 JWT工具类代码:

java 复制代码
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 用户上下文代码:

java 复制代码
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的拦截。

java 复制代码
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 请求业务代码

java 复制代码
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);

    }
}

控制台输出:

java 复制代码
用户id:2
用户名称:hubayi
相关推荐
舌尖上的五香5 分钟前
ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal
java
okok__TXF6 分钟前
Sentinel入门篇【流量治理】
java·sentinel
谁他个天昏地暗8 分钟前
Java 实现 Excel 文件对比与数据填充
java·开发语言·excel
大P哥阿豪25 分钟前
Go defer(二):从汇编的角度理解延迟调用的实现
开发语言·汇编·后端·golang
今天背单词了吗98031 分钟前
算法学习笔记:11.冒泡排序——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·学习·算法·排序算法·冒泡排序
Brookty39 分钟前
【操作系统】进程(二)内存管理、通信
java·linux·服务器·网络·学习·java-ee·操作系统
风象南39 分钟前
SpringBoot 与 HTMX:现代 Web 开发的高效组合
java·spring boot·后端
wstcl2 小时前
让你的asp.net网站在调试模式下也能在局域网通过ip访问
后端·tcp/ip·asp.net
倔强的小石头_4 小时前
【C语言指南】函数指针深度解析
java·c语言·算法
kangkang-7 小时前
PC端基于SpringBoot架构控制无人机(三):系统架构设计
java·架构·无人机