用户登录——>数据加密数据库比对——>生成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;}}
}