<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          API接口的安全設計驗證:ticket,簽名,時間戳

          共 13489字,需瀏覽 27分鐘

           ·

          2021-06-22 22:14


          作者:一劍天門

          cnblogs.com/dslx/p/14116294.html

          概述

          與前端對接的API接口,如果被第三方抓包并進行惡意篡改參數,可能會導致數據泄露,甚至會被篡改數據,我主要圍繞時間戳,token,簽名三個部分來保證API接口的安全性

          1.用戶成功登陸站點后,服務器會返回一個token,用戶的任何操作都必須帶了這個參數,可以將這個參數直接放到header里。

          2.客戶端用需要發(fā)送的參數和token生成一個簽名sign,作為參數一起發(fā)送給服務端,服務端在用同樣的方法生成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即將超時,無感知續(xù)命。延長使用時間,如果用戶在一段時間內沒進行任何操作,就需要重新登錄系統(tǒng)。擴展:記一次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簽名

          把所有的參數拼接一起,在加入系統(tǒng)秘鑰,進行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;
              }

          需要跟當前服務器時間進行對比,如果超過一分鐘,就拒絕本次請求,節(jié)省服務器查詢數據的消耗

          攔截器

          每次請求都帶有這三個參數,我們都需要進行驗證,只有在三個參數都滿足我們的要求,才允許數據返回或被操作。

          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是否即將過期,進行續(xù)命操作
                      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;
              }
          }

          訪問

          先登錄系統(tǒng),獲取合法的ticket

          生成一個合法的sign驗證,獲取測試ts,訪問openDemo,即可正常訪問。還可以將參數加密,將http換成https,就不一 一展開了。

          demo代碼

          https://github.com/hwc4110/spring-demo1221

          END

          推薦好文

          歡迎添加程序汪個人微信 itwang007  進粉絲群或圍觀朋友圈



          往期資源  需要請自取

          Java項目分享 最新整理全集,找項目不累啦 03版

          臥槽!字節(jié)跳動《算法中文手冊》火了,完整版 PDF 開放下載

          字節(jié)跳動總結的設計模式 PDF 火了,完整版開放下載!


          堪稱神級的Spring Boot手冊,從基礎入門到實戰(zhàn)進階


          臥槽!阿里大佬總結的《圖解Java》火了,完整版PDF開放下載!

          喜歡就"在看"唄^_^

          瀏覽 41
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产人妻国产毛片 | 国产亲子乱XXXXinin | 97国产超碰在线观看 | 欧美成人在线免费观看视频 | 色婷婷aⅴ|