用户登录——>数据加密数据库比对——>生成jwt令牌封装返回——>拦截器统一拦截进行jwt校验-并将数据放入本地线程中。
介绍:
ThreadLocal 并不是一个Thread,而是Thread的线程局部变量。 ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
常用方法:
public void set(T value) 设置当前线程的线程局部变量的值
public T get() 返回当前线程所对应的线程局部变量的值
public void remove() 移除当前线程的线程局部变量
常量类方便灵活修改资源或数据。
异常抛出
拦截器时将登录数据存储到线程中(前端每一次调用接口都是一次单独的线程操作)
/**
* 员工管理
*/
@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);
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;
? }
?
}
?
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);
? }
?
}
员工登录拦截
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;
? ? ? }
? }
}
?