SpringBoot+AOP实现验证JWT+全局异常处理


SpringBoot+AOP实现验证JWT+全局异常处理

实现流程原理

  1. 模拟用户发送id密码登录,请求登录接口,登录接口放行
  2. 服务器验证密码,根据用户密码生成token并返回
  3. 模拟用户执行操作,请求测试接口,AOP进行拦截,获取请求携带的token并验证,如通过则放行。

项目结构

引入相关依赖

<dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>1.2.46</version>
      </dependency>

      <dependency>
          <groupId>io.jsonwebtoken</groupId>
          <artifactId>jjwt</artifactId>
          <version>0.9.1</version>
      </dependency>

      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.9.6</version>
      </dependency>

      <dependency>
          <groupId>org.apache.commons</groupId>
          <artifactId>commons-lang3</artifactId>
          <version>3.10</version>
      </dependency>

创建aspect包

创建SafetyAspect类,(aop切面)

import com.alibaba.fastjson.JSONObject;
import com.example.testjwt.config.TokenCheckAnnotation;
import com.example.testjwt.exception.AuthenticateException;
import com.example.testjwt.utils.JwtTokenUtils;
import com.example.testjwt.utils.response.ResponseServer;
import com.example.testjwt.utils.response.ServerEnum;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * description: 切面
 *
 * @author zwq
 * @date 2021/5/15 15:59
 */
@Aspect
@Component
public class SafetyAspect {
    /**
     * Pointcut 切入点
     * 匹配包下面的所有方法
     */
    @Pointcut(value = "execution(public * com.example.testjwt.controller.*.*(..))&& @annotation(tokenCheckAnnotation)")
    public void safetyAspect(TokenCheckAnnotation tokenCheckAnnotation) {
    }

    /**
     * 环绕通知
     */
    @Around(value = "safetyAspect(tokenCheckAnnotation)")
    public Object around(ProceedingJoinPoint pjp, TokenCheckAnnotation tokenCheckAnnotation) {
        System.out.println("截获");
        //验证信息,保证接口安全
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        Object obj = null;

        //方法的形参参数
        Object[] args = pjp.getArgs();

        System.out.println("长度:"+args.length);

        if(args.length!=1||!(JSONObject.class.isInstance(JSONObject.toJSON(args[0])))){
            throw new AuthenticateException(ServerEnum.ERROR);
        }

        JSONObject jsonObject = (JSONObject)args[0];

        System.out.println("元数据");
        System.out.println(jsonObject.toJSONString());

        String token = (String)jsonObject.get("Authorization-token");

        JSONObject data = jsonObject.getJSONObject("data");

        if(data == null){
            throw new AuthenticateException(ServerEnum.ERROR);
        }

        Integer id = (Integer)data.get("id");

		//String pass = id+id+"thisispass";
        String pass = "zwqzwq";

        // 验证token是否为空
        if (!StringUtils.isNotBlank(token)) {
            throw new AuthenticateException(ServerEnum.TOKEN_ISNULL);
        }

        // 验证token是否失效
        ResponseServer responseServer = JwtTokenUtils.resolverToken(token, pass);
        if (responseServer.getCode() != 200) {
            throw new AuthenticateException(ServerEnum.LOGIN_EXPIRED);
        }

        // 执行目标方法
        try {
            args[0] =data;
            obj = pjp.proceed(args);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

        return obj;

    }
}

创建config包

创建TokenCheckAnnotation注解,(自定义注解,用于标志接口是否需要验证)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * description: 是否需要验证注解
 *
 * @author zwq
 * @date 2021/9/8 15:01
 */
@Target(ElementType.METHOD) // 修饰范围
@Retention(RetentionPolicy.RUNTIME) // 用来描述注解的声明周期
public @interface TokenCheckAnnotation {
}

创建exception包

创建AuthenticateException类(处理全局异常)

import com.example.testjwt.utils.response.ServerEnum;

/**
 * description: 异常处理
 *
 * @author zwq
 * @date 2021/9/8 14:57
 */
public class AuthenticateException extends RuntimeException{
    private Integer code;
    public AuthenticateException(ServerEnum serverEnum) {
        super(serverEnum.getMsg());
        this.code=serverEnum.getCode();
    }
    public Integer getCode() {
        return code;
    }

}

创建utils包

创建jwt工具类(生成token,验证token)

import com.example.testjwt.utils.response.ResponseServer;
import com.example.testjwt.utils.response.ServerEnum;
import io.jsonwebtoken.*;
import sun.misc.BASE64Encoder;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * description: jwt
 *
 * @author zwq
 * @date 2021/9/8 14:47
 */
public class JwtTokenUtils {

    public static  String createToken(Map<String,Object> map,String pass){
        //声明头部信息
        Map<String,Object> headerMap=new HashMap<String,Object>();
        headerMap.put("alg","HS256");
        headerMap.put("typ","JWT");
        Map<String,Object> payload=new HashMap<String,Object>();
        payload.putAll(map);
        Long iat=System.currentTimeMillis();
        //设置jwt的失效时间 一分钟
        Long endTime = iat+60000l;

        //签名值就是我们的安全密钥
        String token=Jwts.builder()
                .setHeader(headerMap)
                .setClaims(payload)
                .setExpiration(new Date(endTime))
                .signWith(SignatureAlgorithm.HS256,getSecretKey(pass))
                .compact();
        return token;
    }

    public static ResponseServer resolverToken(String token ,String pass){
        Claims claims=null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(getSecretKey(pass))
                    .parseClaimsJws(token)
                    .getBody();

        }catch (ExpiredJwtException exp){
            System.out.println("token超时,token失效了");
            return ResponseServer.error(ServerEnum.TOKEN_TIMEOUT);
        }catch (SignatureException sing){
            System.out.println("token解析失败");
            return ResponseServer.error(ServerEnum.SAFETY_ERROR);
        }
        return ResponseServer.success(claims);
    }
    private static String getSecretKey(String key){
        return new BASE64Encoder().encode(key.getBytes());
    }

}


创建response包
包里面创建两个类:ResponseServer(返回数据的封装)、ServerEnum(状态码)

/**
 * description: 返回的数据
 *
 * @author zwq
 * @date 2021/9/8 14:56
 */
public class ResponseServer {

    private Integer code;

    private String msg;

    private Object data;


    private ResponseServer(){

    }
    private ResponseServer(Integer code,String msg){
        this.code=code;
        this.msg=msg;

    }
    private ResponseServer(Integer code,String msg,Object data){
        this.code=code;
        this.msg=msg;
        this.data=data;
    }

    /**
     * 返回默认的 成功状态 200
     * @return
     */
    public static  ResponseServer success(){
        return new ResponseServer(ServerEnum.SUCCESS.getCode(),ServerEnum.SUCCESS.getMsg());
    }

    /**
     * 返回默认的带数据 成功状态 200
     * @param data
     * @return
     */
    public static  ResponseServer success(Object data){
        return new ResponseServer(ServerEnum.SUCCESS.getCode(),ServerEnum.SUCCESS.getMsg(),data);
    }

    /**
     * 其他特殊类型的成功状态,
     * @param serverEnum
     * @return
     */
    public static  ResponseServer success(ServerEnum serverEnum){
        return new ResponseServer(serverEnum.getCode(),serverEnum.getMsg());
    }

    /**
     * 带返回数据的其他特殊类型的成功状态
     * @param serverEnum
     * @param data
     * @return
     */
    public static  ResponseServer success(ServerEnum serverEnum,Object data){
        return new ResponseServer(serverEnum.getCode(),serverEnum.getMsg(),data);
    }

    //失败
    public static ResponseServer error(Integer code,String msg){
        return new ResponseServer(code,msg);
    }
    /**
     +     * @return
     */
    public static  ResponseServer error(){
        return new ResponseServer(ServerEnum.ERROR.getCode(),ServerEnum.ERROR.getMsg());
    }

    /**
     * 返回默认的带数据 失败状态 500
     * @param data
     * @return
     */
    public static  ResponseServer error(Object data){
        return new ResponseServer(ServerEnum.ERROR.getCode(),ServerEnum.ERROR.getMsg(),data);
    }

    /**
     * 其他特殊类型的失败状态,
     * @param serverEnum
     * @return
     */
    public static  ResponseServer error(ServerEnum serverEnum){
        return new ResponseServer(serverEnum.getCode(),serverEnum.getMsg());
    }

    /**
     * 带返回数据的其他特殊类型的失败状态
     * @param serverEnum
     * @param data
     * @return
     */
    public static  ResponseServer error(ServerEnum serverEnum,Object data){
        return new ResponseServer(serverEnum.getCode(),serverEnum.getMsg(),data);
    }

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public Object getData() {
        return data;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public void setData(Object data) {
        this.data = data;
    }
}
/**
 * description: 状态码
 * @author zwq
 * @date 2021/9/8 15:00
 */
public enum ServerEnum {
    SUCCESS(200,"操作成功"),
    DEL_DEPT_SCUCCESS(201,"删除部门成功"),
    LOGIN_ISNULL(5000,"用户名或者密码为空"),
    PHONE_ISNULL(5007,"手机号不能为空"),
    USERNAME_NOTEXIST(5001,"用户名输入有误。"),
    PASSWORD_WRONG(5002,"密码输入错误,请检查"),
    LOGIN_SUCCESS(5003,"登陆成功"),
    LOGIN_EXPIRED(5004,"登录超时,请重新登陆"),
    SECRET_ERROR(5005,"传入的token值有误,不能通过签名验证"),
    TOKEN_TIMEOUT(5006,"登录失效,请重新登录"),
    TOKEN_ISNULL(5008,"获取到的Token值为空"),
    NO_MENU_RIGHT(6000,"没有权限访问该菜单,请联系管理员"),
    NOT_DATA(7001,"没有要导出的数据"),
    HTTP_URL_ISNULL(8002,"你传递的URL路径为空了"),
    SERVER_TIMEOUT(8004,"服务连接请求超时"),
    HTTP_ERROR(8003,"接口访问失败"),
    SERVER_STOP(8005,"服务连接不上"),
    SAFETY_ERROR(9000,"接口验签失败"),
    SAFETY_BAD(9001,"接口被非法攻击"),
    SAFETY_TIMEOUT(9002,"接口访问超时"),
    SAFETY_INVALID(9003,"签名值无效"),
    SAFETY_REPLAY_ATTACK(9004,"接口被重放攻击"),
    LOGIN_PHONEORCODE_INNULL(10000,"手机号或者验证码为空了"),
    LOGIN_CODE_ERROR(10001,"手机验证码输入有误"),
    ALL_STOCK_NULL(20001,"商品的库存都不足了"),
    NO_ORDER_TO_PAY(20002,"没有要支付的订单"),
    CRATER_PAY_ERROR(20003,"生成支付二维码失败"),
    PAY_TIMEOUT(20004,"支付超时请刷新页面"),
    ERROR(500,"操作失败");

    private ServerEnum(int code ,String msg){
        this.code=code;
        this.msg=msg;
    }

    private Integer code;

    private String msg;

    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

}

创建pojo包

创建用户类User:

/**
 * description: 用户类
 * @author zwq
 * @date 2021/9/8 14:46
 */
public class User {
    private Long userId;
    private String userName;
    private String password;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

创建controller包

创建全局异常处理类:GlobalExceptionHandler

import com.example.testjwt.exception.AuthenticateException;
import com.example.testjwt.utils.response.ResponseServer;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * description: 全局异常处理
 *
 * @author zwq
 * @date 2021/9/8 14:58
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(AuthenticateException.class)
    public ResponseServer authenticateException(AuthenticateException e, HttpServletRequest request, HttpServletResponse response){
        return ResponseServer.error(e.getCode(),e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ResponseServer exceptionHandler(Exception e,HttpServletRequest request, HttpServletResponse response){
        e.printStackTrace();
        System.out.println("全局异常");
        return ResponseServer.error();

    }
}


创建testController接口:

import com.alibaba.fastjson.JSONObject;
import com.example.testjwt.config.TokenCheckAnnotation;
import com.example.testjwt.utils.JwtTokenUtils;
import com.example.testjwt.utils.response.ResponseServer;
import netscape.javascript.JSObject;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * description: 测试
 * @author zwq
 * @date 2021/9/8 14:34
 */
@RestController
public class TestController {

    @RequestMapping(value = "/get", method = RequestMethod.POST)
    public String getToken(@RequestBody JSONObject jsonObject) {
        JSONObject data = jsonObject.getJSONObject("data");
        boolean a = Integer.valueOf(1).equals(data.get("id"));
        boolean b = "zwqzwq".equals(data.get("pass"));
        System.out.println("a,b:" + a + b);
        if (a && b) {
            Map<String, Object> map;
            map = new HashMap();
            map.put("uid", 1);
            String token = JwtTokenUtils.createToken(map, "zwqzwq");
            System.out.println(token);
            return token;
        }
        return "error";
    }

    @TokenCheckAnnotation
    @RequestMapping(value = "/test", method = RequestMethod.POST)
    public void test(@RequestBody JSONObject jsonObject) {
        System.out.println("接口数据");
        System.out.println(jsonObject.toJSONString());
    }

    @RequestMapping(value = "/test2", method = RequestMethod.POST)
    public ResponseServer test2(@RequestBody JSONObject jsonObject) {
        String sss = (String) jsonObject.get("fdsdf");
        sss.equals("99");
        System.out.println("异常后");

        System.out.println("接口里面");
        return ResponseServer.success();
    }

}

源码

https://github.com/zwqmore/springboot-jwt.git


  目录