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

发布时间:2024年01月16日

用户登录——>数据加密数据库比对——>生成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 {
?
 ? ?@Autowired
 ? ?private EmployeeService employeeService;
 ? ?@Autowired
 ? ?private 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 {
?
 ? ?@Autowired
 ? ?private 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的body
 ? ? ? ?JwtBuilder 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) {
 ? ? ? ?// 得到DefaultJwtParser
 ? ? ? ?Claims 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
 ? ? */
 ? ?@ExceptionHandler
 ? ?public Result exceptionHandler(BaseException ex){
 ? ? ? ?log.error("异常信息:{}", ex.getMessage());
 ? ? ? ?return Result.error(ex.getMessage());
 ?  }
?
 ? ?/**
 ? ? * 处理SQL异常
 ? ? * (唯一值重复)
 ? ? * @param ex
 ? ? * @return
 ? ? */
 ? ?@ExceptionHandler
 ? ?public 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 {
?
 ? ?@Autowired
 ? ?private 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 {
?
 ? ?@Autowired
 ? ?private 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;
 ? ? ?  }
 ?  }
}
?

文章来源:https://blog.csdn.net/qq_63535554/article/details/135626944
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。