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

沙海
沙海
沙海
718
文章
2
评论
2021年3月21日10:49:38
评论
2 6925字阅读23分5秒
摘要

速读摘要

速读摘要

客户端用需要发送的参数和token生成一个签名sign,作为参数一起发送给服务端,服务端在用同样的方法生成sign进行检查是否被篡改。服务端需要对token,时间戳进行验证,只有token有效,时间戳未超时,签名有效才能被放行。登录操作,查看是否有这个用户,用户名和密码匹配即可成功登录。引入一个时间戳参数,保证接口仅在一分钟内有效,需要和客户端时间保持一致。每次请求都带有这三个参数,我们都需要进行验证,只有在三个参数都满足我们的要求,才允许数据返回或被操作。

原文约 2317 | 图片 6 | 建议阅读 5 分钟 | 评价反馈

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

一剑天门 程序员闪充宝

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

作者:一剑天门

cnblogs.com/dslx/p/14116294.html

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

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

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

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

3.但这依然存在问题,可能会被进行恶意无限制访问,这时我们需要引入一个时间戳参数,如果超时即是无效的。

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

开放接口

没有进行任何限制,简单粗暴的访问方式,这样的接口方式一般在开放的应用平台,查天气,查快递,只要你输入正确对应的参数调用,即可获取到自己需要的信息,我们可以任意修改参数值。

 * * 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;    }

重复访问

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

  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

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

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

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

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

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

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

点击阅读全文前往微服务电商教程

阅读原文

继续阅读
weinxin
资源分享QQ群
本站是一个IT技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: