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

48次阅读

共计 5044 个字符,预计需要花费 13 分钟才能阅读完成。

概述

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

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 行将超时,无感知续命。缩短应用工夫,如果用户在一段时间内没进行任何操作,就须要从新登录零碎。扩大:记一次 token 平安认证的实际

@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

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

demo 代码
https://github.com/hwc4110/sp…

作者:一剑天门

cnblogs.com/dslx/p/14116294.html

正文完
 0