API接口的安全设计验证—ticket,签名,时间戳

概述

与前端对接的API接口,若是被第三方抓包并进行恶意篡改参数,可能会致使数据泄露,甚至会被篡改数据,我主要围绕时间戳,token,签名三个部分来保证API接口的安全性前端

1.用户成功登录站点后,服务器会返回一个token,用户的任何操做都必须带了这个参数,能够将这个参数直接放到header里。git

2.客户端用须要发送的参数和token生成一个签名sign,做为参数一块儿发送给服务端,服务端在用一样的方法生成sign进行检查是否被篡改。github

3.但这依然存在问题,可能会被进行恶意无限制访问,这时咱们须要引入一个时间戳参数,若是超时便是无效的。redis

4.服务端须要对token,签名,时间戳进行验证,只有token有效,时间戳未超时,签名有效才能被放行。spring

开放接口

没有进行任何限制,简单粗暴的访问方式,这样的接口方式通常在开放的应用平台,查天气,查快递,只要你输入正确对应的参数调用,便可获取到本身须要的信息,咱们能够任意修改参数值。json

 *
 * Description: 开放的接口
 * @author huangweicheng
 * @date 2020/12/21
*/
@RestController
@RequestMapping("/token")
public class TokenSignController {

    @Autowired
    private TokenSignService tokenSignService;

    @RequestMapping(value = "openDemo",method = RequestMethod.GET)
    public List<PersonEntity> openDemo(int personId){
        return tokenSignService.getPersonList(personId);
    }
}

Token认证获取

用户登陆成功后,会获取一个ticket值,接下去任何接口的访问都须要这个参数。咱们把它放置在redis内,有效期为10分钟,在ticket即将超时,无感知续命。延长使用时间,若是用户在一段时间内没进行任何操做,就须要从新登陆系统。缓存

@RequestMapping(value = "login",method = RequestMethod.POST)
    public JSONObject login(@NotNull String username, @NotNull String password){
        return tokenSignService.login(username,password);
    }

登陆操做,查看是否有这个用户,用户名和密码匹配便可成功登陆。安全

    /** 
     * 
     * Description:验证登陆,ticket成功后放置缓存中,
     * @param
     * @author huangweicheng
     * @date 2020/12/31   
    */ 
    public JSONObject login(String username,String password){
        JSONObject result = new JSONObject();
        PersonEntity personEntity = personDao.findByLoginName(username);
        if (personEntity == null || (personEntity != null && !personEntity.getPassword().equals(password))){
            result.put("success",false);
            result.put("ticket","");
            result.put("code","999");
            result.put("message","用户名和密码不匹配");
            return result;
        }
        if (personEntity.getLoginName().equals(username) && personEntity.getPassword().equals(password)){
            String ticket = UUID.randomUUID().toString();
            ticket = ticket.replace("-","");
            redisTemplate.opsForValue().set(ticket,personEntity.getLoginName(),10L, TimeUnit.MINUTES);
            result.put("success",true);
            result.put("ticket",ticket);
            result.put("code",200);
            result.put("message","登陆成功");
            return result;
        }
        result.put("success",false);
        result.put("ticket","");
        result.put("code","1000");
        result.put("message","未知异常,请重试");
        return result;
    }

Sign签名

把全部的参数拼接一块儿,在加入系统秘钥,进行MD5计算生成一个sign签名,防止参数被人恶意篡改,后台按一样的方法生成秘钥,进行签名对比。服务器

    /**
     * @param request
     * @return
     */
    public static Boolean checkSign(HttpServletRequest request,String sign){
        Boolean flag= false;
        //检查sigin是否过时
        Enumeration<?> pNames =  request.getParameterNames();
        Map<String, String> params = new HashMap<String, String>();
        while (pNames.hasMoreElements()) {
            String pName = (String) pNames.nextElement();
            if("sign".equals(pName)) continue;
            String pValue = (String)request.getParameter(pName);
            params.put(pName, pValue);
        }
        System.out.println("如今的sign-->>" + sign);
        System.out.println("验证的sign-->>" + getSign(params,secretKeyOfWxh));
        if(sign.equals(getSign(params, secretKeyOfWxh))){
            flag = true;
        }
        return flag;
    }

重复访问

引入一个时间戳参数,保证接口仅在一分钟内有效,须要和客户端时间保持一致。app

  public static long getTimestamp(){
        long timestampLong = System.currentTimeMillis();

        long timestampsStr = timestampLong / 1000;

        return timestampsStr;
    }

须要跟当前服务器时间进行对比,若是超过一分钟,就拒绝本次请求,节省服务器查询数据的消耗

拦截器

每次请求都带有这三个参数,咱们都须要进行验证,只有在三个参数都知足咱们的要求,才容许数据返回或被操做。

public class LoginInterceptor implements HandlerInterceptor {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws IOException {
        JSONObject jsonObject = new JSONObject();
        String ticket = request.getParameter("ticket");
        String sign = request.getParameter("sign");
        String ts = request.getParameter("ts");
        if (StringUtils.isEmpty(ticket) || StringUtils.isEmpty(sign) || StringUtils.isEmpty(ts)){
            jsonObject.put("success",false);
            jsonObject.put("message","args is isEmpty");
            jsonObject.put("code","1001");
            PrintWriter printWriter = response.getWriter();
            printWriter.write(jsonObject.toJSONString());
            return false;
        }
        //若是redis存在ticket就认为是合法的请求
        if (redisTemplate.hasKey(ticket)){
            System.out.println(redisTemplate.opsForValue().getOperations().getExpire(ticket));
            String values = (String) redisTemplate.opsForValue().get(ticket);
            //判断ticket是否即将过时,进行续命操做
            if (redisTemplate.opsForValue().getOperations().getExpire(ticket) != -2 && redisTemplate.opsForValue().getOperations().getExpire(ticket) < 20){
                redisTemplate.opsForValue().set(ticket,values,10L, TimeUnit.MINUTES);
            }
            System.out.println(SignUtils.getTimestamp());
            //判断是否重复访问,存在重放攻击的时间窗口期
            if (SignUtils.getTimestamp() - Long.valueOf(ts) > 600){
                jsonObject.put("success",false);
                jsonObject.put("message","Overtime to connect to server");
                jsonObject.put("code","1002");
                PrintWriter printWriter = response.getWriter();
                printWriter.write(jsonObject.toJSONString());
                return false;
            }
            //验证签名
            if (!SignUtils.checkSign(request,sign)){
                jsonObject.put("success",false);
                jsonObject.put("message","sign is invalid");
                jsonObject.put("code","1003");
                PrintWriter printWriter = response.getWriter();
                printWriter.write(jsonObject.toJSONString());
                return false;
            }
            return true;
        }else {
            jsonObject.put("success",false);
            jsonObject.put("message","ticket is invalid,Relogin.");
            jsonObject.put("code","1004");
            PrintWriter printWriter = response.getWriter();
            printWriter.write(jsonObject.toJSONString());
        }
        return false;
    }
}

访问

先登陆系统,获取合法的ticket

生成一个合法的sign验证,获取测试ts,访问openDemo,便可正常访问。还能够将参数加密,将http换成https,就不一 一展开了。

demo代码 https://github.com/hwc4110/spring-demo1221

相关文章
相关标签/搜索