今天发现了一个问题,我们数据用户表中会存在两个或者多个基本信息不同,类型不同,但是账号相同的用户,所以当使用登录时,我们没法确定唯一用户信息,只能通过电话号与该用户在我们系统选择的类型标签来确定唯一用户。不能通过id确定唯一用户。例如:
{id:1,name:张三,账号:电话号码1,密码:123456,用户类型:1}
{id:2,name:张三,账号:电话号码1,密码:123456,用户类型:2}
{id:3,name:李四,账号:电话号码2,密码:123456,用户类型:2}
--token存储的是电话号码
电话号码1,类型1--》吃饭
电话号码1,类型2--》喝水
电话号码2,类型2--》打电话
要通过电话号码,用户类型,才能确定这条数据是吃饭
前端传输token,电话,类型。
如果说张三带着自己的token,输入李四的号码,再加上用户类型2
就能查出打电话,然而这并不是张三的数据
这也就诞生了一个问题,如果用户带着自己的token,配上他知道的别人的电话号码,就能查出别人的信息。因为电话号码不算是隐私的。所以就出现了这一类的安全隐患。
为了解决这种安全隐患,我想到了接口签名的方法,因为不想改变以前的接口,但却又要用签名方法的话,我想到的方法就是aop,但是aop找切面,切点是很麻烦的。毕竟不可能在切面所有的将所有需要接口签名校验的写进去。然后因为security有注解控制权限的方式然后去b站搜了一下aop注解控制权限得到灵感。想到了用注解标识切点,而且用注解的话可以解析注解字段,可以自定义那些字段加密验签,也就增加了灵活性和破解的困难性。
下面就是实现过程
首先自定义加密的注解
package com.hisicom.qymh.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//注解用于方法
@Target(ElementType.METHOD)
//注解用于运行时
@Retention(RetentionPolicy.RUNTIME)
public @interface SecretCheck {
String secret() default "";
}
通过创建切面
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
@Aspect
@Component
public class SecretCheckAop {
/**
* 定义切点在使用@SecretCheck的方法
*/
@Pointcut("@annotation(com.hisicom.qymh.annotation.SecretCheck))")
public void pointCut(){
}
/**
* 方法环绕切点增强功能
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("pointCut()")
public AjaxResult Around(ProceedingJoinPoint joinPoint) throws Throwable {
//获取到HttpServletRequest,ThreadLocal可以读取上下文的请求
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
HttpServletRequest request = servletRequestAttributes.getRequest();
//获取签名
String sign = (String) request.getParameter("sign");
//获取切点方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取方法
Method method = signature.getMethod();
//获取方法上的注解
SecretCheck secretCheck = method.getDeclaredAnnotation(SecretCheck.class);
//获取注解的字段,用,切分成字符串数组
String[] strings = secretCheck.secret().split(",");
String checkPass = "";
//用字段名称,获取前端传来的值
for (String string : strings) {
String filde = request.getParameter(string);
checkPass += filde+"&adminpass";
}
//有注解不带sign,拒绝访问
if (Objects.isNull(sign)){
return AjaxResult.error("非法请求");
}else{
//验证签名
Boolean s = sign.equals(DigestUtils.md5DigestAsHex(checkPass.getBytes()));
if(s){
return (AjaxResult) joinPoint.proceed();
}else {
return AjaxResult.error("非法请求");
}
}
}
}
使用方式
@GetMapping("/getjcdata")
//标识用certNo,和busiId加密
@SecretCheck(secret = "certNo,busiId")
public AjaxResult getjcData(@RequestParam("certNo") String certNo,@RequestParam("busiId") String busiId) {
List<String> lists = jcCorpInfoService.countByCertNoAndBusiId(certNo, busiId);
return AjaxResult.success(jcCorpInfo);
}
前端请求方式
export function secretGetCorpInfo(data){
let pass = md5(data.certNo+"&adminpass"+data.busiId+"&adminpass")
return request({
method: "GET",
url: "/getjcdata",
params: {
certNo: data.certNo,
busiId: data.busiId,
sign: pass
},
})
}
测试结果
测试成功。
初出茅庐很多理解不深刻,若有隐患,优化方式,请大家不吝赐教。谢谢