认证规则
在http请求头headers中添加token和timestamp参数
token = MD5(密钥+当前时间戳),密钥:xxxxxx
timestamp = 当前时间戳
代码干货
sign结构,message的异常拦截和返回结果封装可以改成用自己
1.自定义注解
package com.horizon.sign.aop;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author haojw
* 请求认证
*/
@Retention(value = RetentionPolicy.RUNTIME)
public @interface SignatureValidation {
}
2.实现aop切点
package com.horizon.sign.aspecct;
import com.horizon.sign.config.AppConfig;
import com.horizon.message.exception.ErrorCodeException;
import com.horizon.message.MessageCodeEnum;
import com.horizon.sign.util.MD5Utils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
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;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
* @author haojw
*/
@Aspect
@Component
public class SignatureValidation {
/**
* 时间戳请求最大限制
*/
private static final long MAX_REQUEST = 30000 * 1000L;
/**
* 配置文件
*/
@Resource
private AppConfig appConfig;
/**
* 验签切点(前两个不需要注解)
*/
//@Pointcut("execution(* com.horizon.business..*Controller.*(..))")//com.horizon.business包及子包下所有Controller后缀的类的方法
//@Pointcut("execution(* com.horizon.business..*.*(..))")//com.horizon.business包及子包下所以类和方法
@Pointcut("@annotation(com.horizon.sign.aop.SignatureValidation)")//有SignatureValidation注解的方法
//@Pointcut("@within(com.horizon.sign.aop.SignatureValidation)")//有SignatureValidation注解的类
private void verifyUserKey() {
}
/**
* 开始验签
*/
@Before("verifyUserKey()")
public void doBasicProfiling() {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
String token = request.getHeader("token");
String timestamp = request.getHeader("timestamp");
try {
Boolean check = checkToken(token, timestamp);
if (!check) {
throw new ErrorCodeException(MessageCodeEnum.SIGN_ERROR);
}
} catch (Throwable throwable) {
throw new ErrorCodeException(MessageCodeEnum.SIGN_ERROR);
}
}
/**
* 校验token
*
* @param token 签名
* @param timestamp 时间戳
* @return 校验结果
*/
private Boolean checkToken(String token, String timestamp) {
if (StringUtils.isAnyBlank(token, timestamp)) {
return false;
}
long now = System.currentTimeMillis();
long time = Long.parseLong(timestamp);
if (now - time > MAX_REQUEST) {
//log.error("时间戳已过期[{}][{}][{}]", now, time, (now - time));
return false;
}
String secret = StringUtils.substring(appConfig.getAppSecret(), 0, 32);
String crypt = MD5Utils.getMD5(secret + timestamp);
if (!StringUtils.equals(crypt, token)) {
//log.error("请求token[{}],timestamp[{}]-vs-服务器token[{}]", token, timestamp, crypt);
return false;
}
return true;
}
}
3.注入配置里的签名密钥
package com.horizon.sign.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author:haojw
* @date: 2021/6/5
* @time: 16:08
*/
@Data
@Component
@ConfigurationProperties(prefix = "app-config")
public class AppConfig {
/**
* 签名秘钥
*/
private String appSecret;
}
4.MD5加密工具类
package com.horizon.sign.util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* 此类是MD5加密算法的实现, 采用了java内置算法,需要引用java.security.MessageDigest
*
* @author airland.congs
* @version $Revision: 1.1 $
*/
public class MD5Utils {
/**
* 小写的字符串
*/
private static char[] DigitLower = {'0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* 大写的字符串
*/
private static char[] DigitUpper = {'0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
/**
* 默认构造函数
*/
public MD5Utils() {
}
/**
* 加密之后的字符串全为小写
*
* @param srcStr
* @return
* @throws NoSuchAlgorithmException
* @throws NullPointerException
*/
public static String getMD5Lower(String srcStr)
throws NoSuchAlgorithmException {
String sign = "lower";
return processStr(srcStr, sign);
}
/**
* 加密之后的字符串全为大写
*
* @param srcStr
* @return
* @throws NoSuchAlgorithmException
* @throws NullPointerException
*/
public static String getMD5Upper(String srcStr)
throws NoSuchAlgorithmException {
String sign = "upper";
return processStr(srcStr, sign);
}
private static String processStr(String srcStr, String sign)
throws NoSuchAlgorithmException, NullPointerException {
MessageDigest digest;
// 定义调用的方法
String algorithm = "MD5";
// 结果字符串
String result = "";
// 初始化并开始进行计算
digest = MessageDigest.getInstance(algorithm);
digest.update(srcStr.getBytes());
byte[] byteRes = digest.digest();
// 计算byte数组的长度
int length = byteRes.length;
// 将byte数组转换成字符串
for (int i = 0; i < length; i++) {
result = result + byteHEX(byteRes[i], sign);
}
return result;
}
/**
* 将btye数组转换成字符串
*
* @param bt
* @return
*/
private static String byteHEX(byte bt, String sign) {
char[] temp = null;
if (sign.equalsIgnoreCase("lower")) {
temp = DigitLower;
} else if (sign.equalsIgnoreCase("upper")) {
temp = DigitUpper;
} else {
throw new RuntimeException("加密缺少必要的条件");
}
char[] ob = new char[2];
ob[0] = temp[(bt >>> 4) & 0X0F];
ob[1] = temp[bt & 0X0F];
return new String(ob);
}
public static String getMD5(String content) {
MessageDigest messageDigest = null;
try {
messageDigest = MessageDigest.getInstance("MD5");
messageDigest.reset();
messageDigest.update(content.getBytes("UTF-8"));
} catch (Exception e) {
e.printStackTrace();
}
byte[] byteArray = messageDigest.digest();
StringBuffer md5StrBuff = new StringBuffer();
for (int i = 0; i < byteArray.length; i++) {
if (Integer.toHexString(0xFF & byteArray[i]).length() == 1) {
md5StrBuff.append("0").append(Integer.toHexString(0xFF & byteArray[i]));
} else {
md5StrBuff.append(Integer.toHexString(0xFF & byteArray[i]));
}
}
return md5StrBuff.toString();
}
}
5.异常拦截及返回结果的封装
package com.horizon.message.exception;
import com.horizon.message.MessageCodeEnum;
/**
* @author haojw
*/
@SuppressWarnings("unused")
public class ErrorCodeException extends RuntimeException {
private static final long serialVersionUID = -7638041501183925225L;
private Integer code;
public ErrorCodeException(MessageCodeEnum errorCode, String msg) {
super(msg);
this.code = errorCode.getCode();
}
public ErrorCodeException(Integer code, String msg) {
super(msg);
this.code = code;
}
public ErrorCodeException(MessageCodeEnum errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
}
public ErrorCodeException(String msg) {
super(msg);
this.code = MessageCodeEnum.NO.getCode();
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
package com.horizon.message.exception;
import com.horizon.message.MessageBean;
import com.horizon.message.MessageCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* 全局异常处理
*
* @author haojw
*/
@SuppressWarnings("Duplicates")
@Slf4j
@ControllerAdvice
public class WebExceptionHandler {
/**
* 不支持
*/
private final static String NOT_SUPPORTED = "not supported";
/**
* 缺少参数
*/
private final static String INVALID_PARAMS = "parameter";
/**
* 自定义异常
*
* @param request
* @param e
* @return
*/
@ExceptionHandler(value = ErrorCodeException.class)
@ResponseBody
public MessageBean myErrorHandler(HttpServletRequest request, ErrorCodeException e) {
MessageBean message = new MessageBean();
log.error("[{}]接口异常[{}]", request.getRequestURI(), e.getMessage());
message.setCode(e.getCode());
message.setMsg(e.getMessage());
return message;
}
/**
* 系统异常
*
* @param request
* @param ex
* @return
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public MessageBean errorHandler(HttpServletRequest request, Exception ex) {
MessageBean message = new MessageBean();
log.error("[{}]系统异常", request.getRequestURI(), ex);
// 请求方式错误
if (ex.getMessage().contains(NOT_SUPPORTED)) {
return new MessageBean(MessageCodeEnum.NOT_SUPPORT);
} else if (ex.getMessage().contains(INVALID_PARAMS)) {
return new MessageBean(MessageCodeEnum.INVALID_PARAMS);
}
message.setCode(MessageCodeEnum.ERROR.getCode());
message.setMsg(MessageCodeEnum.ERROR.getMessage());
return message;
}
}
package com.horizon.message;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 接口返回信息
*
* @author haojw
*/
@Data
@NoArgsConstructor
@SuppressWarnings("unused")
public class MessageBean<T> implements Serializable {
private static final long serialVersionUID = 7192766535561421181L;
private String msg;
private T data;
private Integer code;
public MessageBean(MessageCodeEnum errorCode, T data, String errorMsg) {
this.code = errorCode.getCode();
this.data = data;
this.msg = errorMsg;
}
public MessageBean(MessageCodeEnum errorCode, String errorMsg) {
this.code = errorCode.getCode();
this.msg = errorMsg;
}
public MessageBean(MessageCodeEnum errorCode) {
this.code = errorCode.getCode();
this.msg = errorCode.getMessage();
}
}
package com.horizon.message;
/**
* 全局http错误编码
*
* @author haojw
*/
@SuppressWarnings("unused")
public enum MessageCodeEnum {
/**
* 常规错误码
*/
INVALID_PARAMS(9001, "参数有误"),
NOT_SUPPORT(9002, "请求方式错误"),
FREQUENTLY_REQUEST(9003, "操作频繁"),
HTTP_CONNECTION_OVERTIME(9998, "连接超时"),
ERROR(9999, "系统异常"),
SIGN_ERROR(1000, "签名异常"),
/**
* 泛用错误码
*/
OK(200, "请求通过"),
NO(201, "请求不通过");
private int code;
private String message;
MessageCodeEnum(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
6.注解应用
版权声明:本文为u011433170原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。