SpringBoot+AOP实现验证JWT+全局异常处理
实现流程原理
- 模拟用户发送id密码登录,请求登录接口,登录接口放行
- 服务器验证密码,根据用户密码生成token并返回
- 模拟用户执行操作,请求测试接口,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();
}
}