当前位置: 首页 > news >正文

**登录+JWT+异常处理+拦截器+ThreadLocal-开发思想与代码实现**

用户登录——>数据加密数据库比对——>生成jwt令牌封装返回——>拦截器统一拦截进行jwt校验-并将数据放入本地线程中。

0、 ThreadLocal

介绍:

ThreadLocal 并不是一个Thread,而是Thread的线程局部变量。 ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。

常用方法:

  • public void set(T value) 设置当前线程的线程局部变量的值

  • public T get() 返回当前线程所对应的线程局部变量的值

  • public void remove() 移除当前线程的线程局部变量

  • 常量类方便灵活修改资源或数据。

  • 异常抛出

  • 拦截器时将登录数据存储到线程中(前端每一次调用接口都是一次单独的线程操作)

1、业务逻辑代码开发
/*** 员工管理*/
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Api(tags = "员工相关接口")
public class EmployeeController {
​@Autowiredprivate EmployeeService employeeService;@Autowiredprivate JwtProperties jwtProperties;
​/*** 登录** @param employeeLoginDTO* @return*/@PostMapping("/login")@ApiOperation(value = "员工登录")public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {log.info("员工登录:{}", employeeLoginDTO);
​Employee employee = employeeService.login(employeeLoginDTO);
​//登录成功后,生成jwt令牌Map<String, Object> claims = new HashMap<>();
​claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
​String token = JwtUtil.createJWT(jwtProperties.getAdminSecretKey(),jwtProperties.getAdminTtl(),claims);
​//构建封装还回前端对象EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder().id(employee.getId()).userName(employee.getUsername()).name(employee.getName()).token(token).build();
​return Result.success(employeeLoginVO);}

public interface EmployeeService {
​/*** 员工登录* @param employeeLoginDTO* @return*/Employee login(EmployeeLoginDTO employeeLoginDTO);

@Service
public class EmployeeServiceImpl implements EmployeeService {
​@Autowiredprivate EmployeeMapper employeeMapper;
​/*** 员工登录** @param employeeLoginDTO* @return*/public Employee login(EmployeeLoginDTO employeeLoginDTO) {String username = employeeLoginDTO.getUsername();String password = employeeLoginDTO.getPassword();
​//1、根据用户名查询数据库中的数据Employee employee = employeeMapper.getByUsername(username);
​//2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)if (employee == null) {//账号不存在throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);}
​//密码比对//对前端传过来的明文密码进行md5加密处理password = DigestUtils.md5DigestAsHex(password.getBytes());if (!password.equals(employee.getPassword())) {//密码错误throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);}
​if (employee.getStatus() == StatusConstant.DISABLE) {//账号被锁定throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);}
​//3、返回实体对象return employee;}

@Mapper
public interface EmployeeMapper {
​/*** 根据用户名查询员工* @param username* @return*/@Select("select * from employee where username = #{username}")Employee getByUsername(String username);

2、工具类

JWT工具类

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
​
public class JwtUtil {/*** 生成jwt* 使用Hs256算法, 私匙使用固定秘钥** @param secretKey jwt秘钥* @param ttlMillis jwt过期时间(毫秒)* @param claims    设置的信息* @return*/public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {// 指定签名的时候使用的签名算法,也就是header那部分SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
​// 生成JWT的时间long expMillis = System.currentTimeMillis() + ttlMillis;Date exp = new Date(expMillis);
​// 设置jwt的bodyJwtBuilder builder = Jwts.builder()// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的.setClaims(claims)// 设置签名使用的签名算法和签名使用的秘钥.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))// 设置过期时间.setExpiration(exp);
​return builder.compact();}
​/*** Token解密** @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个* @param token     加密后的token* @return*/public static Claims parseJWT(String secretKey, String token) {// 得到DefaultJwtParserClaims claims = Jwts.parser()// 设置签名的秘钥.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))// 设置需要解析的jwt.parseClaimsJws(token).getBody();return claims;}
​
}
​

3、ThreadLocal线程操作工具
public class BaseContext {
​public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
​public static void setCurrentId(Long id) {threadLocal.set(id);}
​public static Long getCurrentId() {return threadLocal.get();}
​public static void removeCurrentId() {threadLocal.remove();}
​
}

全局异常处理器

捕获sql异常和业务异常

/*** 全局异常处理器,处理项目中抛出的业务异常*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
​/*** 捕获业务异常* @param ex* @return*/@ExceptionHandlerpublic Result exceptionHandler(BaseException ex){log.error("异常信息:{}", ex.getMessage());return Result.error(ex.getMessage());}
​/*** 处理SQL异常* (唯一值重复)* @param ex* @return*/@ExceptionHandlerpublic Result exceptionHandler(SQLIntegrityConstraintViolationException ex){//Duplicate entry 'zhangsan' for key 'employee.idx_username'String message = ex.getMessage();if(message.contains("Duplicate entry")){String[] split = message.split(" ");String username = split[2];String msg = username + MessageConstant.ALREADY_EXISTS;return Result.error(msg);}else{return Result.error(MessageConstant.UNKNOWN_ERROR);}}
}

常量类

用于区分员工与用户。

public class JwtClaimsConstant {
​public static final String EMP_ID = "empId";//员工(可登录pc)public static final String USER_ID = "userId";//用户//public static final String PHONE = "phone";//public static final String USERNAME = "username";//public static final String NAME = "name";
​
}
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
​
@Component
@ConfigurationProperties(prefix = "sky.jwt")
@Data
public class JwtProperties {
​/*** 管理端员工生成jwt令牌相关配置*/private String adminSecretKey;private long adminTtl;private String adminTokenName;
​/*** 用户端微信用户生成jwt令牌相关配置*/private String userSecretKey;private long userTtl;private String userTokenName;
​
}

异常类
/*** 业务异常*/
public class BaseException extends RuntimeException {
​public BaseException() {}
​public BaseException(String msg) {super(msg);}
​
}

4、拦截器

员工登录拦截

import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
​
/*** jwt令牌校验的拦截器*/
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {
​@Autowiredprivate JwtProperties jwtProperties;
​/*** 校验jwt** @param request* @param response* @param handler* @return* @throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断当前拦截到的是Controller的方法还是其他资源if (!(handler instanceof HandlerMethod)) {//当前拦截到的不是动态方法,直接放行return true;}
​//1、从请求头中获取令牌String token = request.getHeader(jwtProperties.getAdminTokenName());
​//2、校验令牌try {log.info("jwt校验:{}", token);Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());log.info("当前员工id:", empId);
​//将登录数据存储到线程中BaseContext.setCurrentId(empId);//3、通过,放行return true;} catch (Exception ex) {//4、不通过,响应401状态码response.setStatus(401);return false;}}
}

用户登录拦截

import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
​
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
​
/*** jwt令牌校验的拦截器*/
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {
​@Autowiredprivate JwtProperties jwtProperties;
​/*** 校验jwt** @param request* @param response* @param handler* @return* @throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断当前拦截到的是Controller的方法还是其他资源if (!(handler instanceof HandlerMethod)) {//当前拦截到的不是动态方法,直接放行return true;}
​//1、从请求头中获取令牌String token = request.getHeader(jwtProperties.getUserTokenName());
​//2、校验令牌try {log.info("jwt校验:{}", token);Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());log.info("当前用户的id:", userId);BaseContext.setCurrentId(userId);//3、通过,放行return true;} catch (Exception ex) {//4、不通过,响应401状态码response.setStatus(401);return false;}}
}
​

相关文章:

  • 省略文字,动态行,查看更多显示全部 组件
  • 雷达信号处理——恒虚警检测(CFAR)
  • 系列五、Spring Security中的认证 授权(前后端分离)
  • 基于elementUI的el-table组件实现按住某一行数据上下滑动选中/选择或取消选中/选择鼠标经过的行
  • 北斗卫星技术在建筑监测领域的革新实践
  • 最新使用宝塔反代openai官方API接口搭建详细教程及502 Bad Gateway错误问题解决
  • MySQL修炼手册7:数据修改基础:INSERT、UPDATE、DELETE语句详解
  • SpringBoot外部配置文件
  • Cesium 模型压平
  • HTTP超文本传输协议
  • 广东省第三届职业技能大赛“网络安全项目”B模块任务书
  • 【计算机网络 谢希仁 第八版笔记】第一章 概述
  • Python——VScode安装
  • Excel地址
  • 使用Qt连接scrcpy-server控制手机
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • angular学习第一篇-----环境搭建
  • css选择器
  • Just for fun——迅速写完快速排序
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • QQ浏览器x5内核的兼容性问题
  • 阿里研究院入选中国企业智库系统影响力榜
  • 初识 beanstalkd
  • 分享自己折腾多时的一套 vue 组件 --we-vue
  • 基于web的全景—— Pannellum小试
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • 数据可视化之下发图实践
  • # Swust 12th acm 邀请赛# [ A ] A+B problem [题解]
  • # 飞书APP集成平台-数字化落地
  • ###C语言程序设计-----C语言学习(3)#
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (LeetCode 49)Anagrams
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转)Unity3DUnity3D在android下调试
  • (转)编辑寄语:因为爱心,所以美丽
  • (最完美)小米手机6X的Usb调试模式在哪里打开的流程
  • *p++,*(p++),*++p,(*p)++区别?
  • ..回顾17,展望18
  • .desktop 桌面快捷_Linux桌面环境那么多,这几款优秀的任你选
  • .MSSQLSERVER 导入导出 命令集--堪称经典,值得借鉴!
  • .net core 依赖注入的基本用发
  • .net framework4与其client profile版本的区别
  • .Net7 环境安装配置
  • .Net的C#语言取月份数值对应的MonthName值
  • .NET精简框架的“无法找到资源程序集”异常释疑
  • .net使用excel的cells对象没有value方法——学习.net的Excel工作表问题
  • .Net语言中的StringBuilder:入门到精通
  • .sh文件怎么运行_创建优化的Go镜像文件以及踩过的坑
  • /proc/stat文件详解(翻译)
  • /var/log/cvslog 太大
  • [.NET 即时通信SignalR] 认识SignalR (一)
  • [Angular] 笔记 9:list/detail 页面以及@Output
  • [BT]BUUCTF刷题第8天(3.26)