最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Boot接口設(shè)計防篡改、防重放攻擊詳解

 更新時間:2019年07月28日 15:04:22   作者:云天  
這篇文章主要給大家介紹了關(guān)于Spring Boot接口設(shè)計防篡改、防重放攻擊的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧

本示例主要內(nèi)容

  • 請求參數(shù)防止篡改攻擊
  • 基于timestamp方案,防止重放攻擊
  • 使用swagger接口文檔自動生成

API接口設(shè)計

API接口由于需要供第三方服務(wù)調(diào)用,所以必須暴露到外網(wǎng),并提供了具體請求地址和請求參數(shù),為了防止被別有用心之人獲取到真實請求參數(shù)后再次發(fā)起請求獲取信息,需要采取很多安全機制。

  • 需要采用https方式對第三方提供接口,數(shù)據(jù)的加密傳輸會更安全,即便是被破解,也需要耗費更多時間
  • 需要有安全的后臺驗證機制,達到防參數(shù)篡改+防二次請求(本示例內(nèi)容)

防止重放攻擊必須要保證請求只在限定的時間內(nèi)有效,需要通過在請求體中攜帶當(dāng)前請求的唯一標(biāo)識,并且進行簽名防止被篡改,所以防止重放攻擊需要建立在防止簽名被串改的基礎(chǔ)之上

防止篡改

  • 客戶端使用約定好的秘鑰對傳輸參數(shù)進行加密,得到簽名值sign1,并且將簽名值存入headers,發(fā)送請求給服務(wù)端
  • 服務(wù)端接收客戶端的請求,通過過濾器使用約定好的秘鑰對請求的參數(shù)(headers除外)再次進行簽名,得到簽名值sign2。
  • 服務(wù)端對比sign1和sign2的值,如果對比一致,認(rèn)定為合法請求。如果對比不一致,說明參數(shù)被篡改,認(rèn)定為非法請求

基于timestamp的方案,防止重放

每次HTTP請求,headers都需要加上timestamp參數(shù),并且timestamp和請求的參數(shù)一起進行數(shù)字簽名。因為一次正常的HTTP請求,從發(fā)出到達服務(wù)器一般都不會超過60s,所以服務(wù)器收到HTTP請求之后,首先判斷時間戳參數(shù)與當(dāng)前時間相比較,是否超過了60s,如果超過了則提示簽名過期(這個過期時間最好做成配置)。

一般情況下,黑客從抓包重放請求耗時遠遠超過了60s,所以此時請求中的timestamp參數(shù)已經(jīng)失效了。

如果黑客修改timestamp參數(shù)為當(dāng)前的時間戳,則sign參數(shù)對應(yīng)的數(shù)字簽名就會失效,因為黑客不知道簽名秘鑰,沒有辦法生成新的數(shù)字簽名(前端一定要保護好秘鑰和加密算法)。

相關(guān)核心思路代碼

過濾器

@Slf4j
@Component
/**
 * 防篡改、防重放攻擊過濾器
 */
public class SignAuthFilter implements Filter {
  @Autowired
  private SecurityProperties securityProperties;

  @Override
  public void init(FilterConfig filterConfig) {
    log.info("初始化 SignAuthFilter");
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    // 防止流讀取一次后就沒有了, 所以需要將流繼續(xù)寫出去
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletRequest requestWrapper = new RequestWrapper(httpRequest);

    Set<String> uriSet = new HashSet<>(securityProperties.getIgnoreSignUri());
    String requestUri = httpRequest.getRequestURI();
    boolean isMatch = false;
    for (String uri : uriSet) {
      isMatch = requestUri.contains(uri);
      if (isMatch) {
        break;
      }
    }
    log.info("當(dāng)前請求的URI是==>{},isMatch==>{}", httpRequest.getRequestURI(), isMatch);
    if (isMatch) {
      filterChain.doFilter(requestWrapper, response);
      return;
    }

    String sign = requestWrapper.getHeader("Sign");
    Long timestamp = Convert.toLong(requestWrapper.getHeader("Timestamp"));

    if (StrUtil.isEmpty(sign)) {
      returnFail("簽名不允許為空", response);
      return;
    }

    if (timestamp == null) {
      returnFail("時間戳不允許為空", response);
      return;
    }

    //重放時間限制(單位分)
    Long difference = DateUtil.between(DateUtil.date(), DateUtil.date(timestamp * 1000), DateUnit.MINUTE);
    if (difference > securityProperties.getSignTimeout()) {
      returnFail("已過期的簽名", response);
      log.info("前端時間戳:{},服務(wù)端時間戳:{}", DateUtil.date(timestamp * 1000), DateUtil.date());
      return;
    }

    boolean accept = true;
    SortedMap<String, String> paramMap;
    switch (requestWrapper.getMethod()) {
      case "GET":
        paramMap = HttpUtil.getUrlParams(requestWrapper);
        accept = SignUtil.verifySign(paramMap, sign, timestamp);
        break;
      case "POST":
      case "PUT":
      case "DELETE":
        paramMap = HttpUtil.getBodyParams(requestWrapper);
        accept = SignUtil.verifySign(paramMap, sign, timestamp);
        break;
      default:
        accept = true;
        break;
    }
    if (accept) {
      filterChain.doFilter(requestWrapper, response);
    } else {
      returnFail("簽名驗證不通過", response);
    }
  }

  private void returnFail(String msg, ServletResponse response) throws IOException {
    response.setCharacterEncoding("UTF-8");
    response.setContentType("application/json; charset=utf-8");
    PrintWriter out = response.getWriter();
    String result = JSONObject.toJSONString(AjaxResult.fail(msg));
    out.println(result);
    out.flush();
    out.close();
  }

  @Override
  public void destroy() {
    log.info("銷毀 SignAuthFilter");
  }
}

簽名驗證

@Slf4j
public class SignUtil {

  /**
   * 驗證簽名
   *
   * @param params
   * @param sign
   * @return
   */
  public static boolean verifySign(SortedMap<String, String> params, String sign, Long timestamp) {
    String paramsJsonStr = "Timestamp" + timestamp + JSONObject.toJSONString(params);
    return verifySign(paramsJsonStr, sign);
  }

  /**
   * 驗證簽名
   *
   * @param params
   * @param sign
   * @return
   */
  public static boolean verifySign(String params, String sign) {
    log.info("Header Sign : {}", sign);
    if (StringUtils.isEmpty(params)) {
      return false;
    }
    log.info("Param : {}", params);
    String paramsSign = getParamsSign(params);
    log.info("Param Sign : {}", paramsSign);
    return sign.equals(paramsSign);
  }

  /**
   * @return 得到簽名
   */
  public static String getParamsSign(String params) {
    return DigestUtils.md5DigestAsHex(params.getBytes()).toUpperCase();
  }
}

不做簽名驗證的接口做成配置(application.yml)

spring:
 security:
  # 簽名驗證超時時間
  signTimeout: 300
  # 允許未簽名訪問的url地址
  ignoreSignUri:
   - /swagger-ui.html
   - /swagger-resources
   - /v2/api-docs
   - /webjars/springfox-swagger-ui
   - /csrf

屬性代碼(SecurityProperties.java)

@Component
@ConfigurationProperties(prefix = "spring.security")
@Data
public class SecurityProperties {

  /**
   * 允許忽略簽名地址
   */
  List<String> ignoreSignUri;

  /**
   * 簽名超時時間(分)
   */
  Integer signTimeout;
}

簽名測試控制器

@RestController
@Slf4j
@RequestMapping("/sign")
@Api(value = "簽名controller", tags = {"簽名測試接口"})
public class SignController {

  @ApiOperation("get測試")
  @ApiImplicitParams({
      @ApiImplicitParam(name = "username", value = "用戶名", required = true, dataType = "String"),
      @ApiImplicitParam(name = "password", value = "密碼", required = true, dataType = "String")
  })
  @GetMapping("/testGet")
  public AjaxResult testGet(String username, String password) {
    log.info("username:{},password:{}", username, password);
    return AjaxResult.success("GET參數(shù)檢驗成功");
  }

  @ApiOperation("post測試")
  @ApiImplicitParams({
      @ApiImplicitParam(name = "data", value = "測試實體", required = true, dataType = "TestVo")
  })
  @PostMapping("/testPost")
  public AjaxResult<TestVo> testPost(@Valid @RequestBody TestVo data) {
    return AjaxResult.success("POST參數(shù)檢驗成功", data);
  }

  @ApiOperation("put測試")
  @ApiImplicitParams({
      @ApiImplicitParam(name = "id", value = "編號", required = true, dataType = "Integer"),
      @ApiImplicitParam(name = "data", value = "測試實體", required = true, dataType = "TestVo")
  })
  @PutMapping("/testPut/{id}")
  public AjaxResult testPut(@PathVariable Integer id, @RequestBody TestVo data) {
    data.setId(id);
    return AjaxResult.success("PUT參數(shù)檢驗成功", data);
  }

  @ApiOperation("delete測試")
  @ApiImplicitParams({
      @ApiImplicitParam(name = "idList", value = "編號列表", required = true, dataType = "List<Integer> ")
  })
  @DeleteMapping("/testDelete")
  public AjaxResult testDelete(@RequestBody List<Integer> idList) {
    return AjaxResult.success("DELETE參數(shù)檢驗成功", idList);
  }
}

前端js請求示例

var settings = {
 "async": true,
 "crossDomain": true,
 "url": "http://localhost:8080/sign/testGet?username=abc&password=123",
 "method": "GET",
 "headers": {
  "Sign": "46B1990701BCF090E3E6E517751DB02F",
  "Timestamp": "1564126422",
  "User-Agent": "PostmanRuntime/7.15.2",
  "Accept": "*/*",
  "Cache-Control": "no-cache",
  "Postman-Token": "a9d10ef5-283b-4ed3-8856-72d4589fb61d,6e7fa816-000a-4b29-9882-56d6ae0f33fb",
  "Host": "localhost:8080",
  "Cookie": "SESSION=OWYyYzFmMDMtODkyOC00NDg5LTk4ZTYtODNhYzcwYjQ5Zjg2",
  "Accept-Encoding": "gzip, deflate",
  "Connection": "keep-alive",
  "cache-control": "no-cache"
 }
}

$.ajax(settings).done(function (response) {
 console.log(response);
});

注意事項

  • 該示例沒有設(shè)置秘鑰,只做了參數(shù)升排然后創(chuàng)建md5簽名
  • 示例請求的參數(shù)md5原文本為:Timestamp1564126422{"password":"123","username":"abc"}
  • 注意headers請求頭帶上了Sign和Timestamp參數(shù)
  • js讀取的Timestamp必須要在服務(wù)端獲取
  • 該示例不包括分布試環(huán)境下,多臺服務(wù)器時間同步問題

自動生成接口文檔

配置代碼

@Configuration
@EnableSwagger2
public class Swagger2Config {
  @Bean
  public Docket createRestApi() {
    return new Docket(DocumentationType.SWAGGER_2)
        .apiInfo(apiInfo())
        .select()
        .apis(RequestHandlerSelectors.basePackage("com.easy.sign"))
        .paths(PathSelectors.any())
        .build();
  }

  //構(gòu)建 api文檔的詳細信息函數(shù),注意這里的注解引用的是哪個
  private ApiInfo apiInfo() {
    return new ApiInfoBuilder()
        .title("簽名示例")
        .contact(new Contact("簽名示例網(wǎng)站", "http://www.baidu.com", "test@qq.com"))
        .version("1.0.0")
        .description("簽名示例接口描述")
        .build();
  }
}

自動生成文檔地址:http://localhost:8080/swagger-ui.html

資料

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。

相關(guān)文章

  • RocketMQ實現(xiàn)隨緣分BUG小功能示例詳解

    RocketMQ實現(xiàn)隨緣分BUG小功能示例詳解

    這篇文章主要為大家介紹了RocketMQ實現(xiàn)隨緣分BUG小功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • 設(shè)置JavaScript自動提示-Eclipse/MyEclipse

    設(shè)置JavaScript自動提示-Eclipse/MyEclipse

    自動提示需要2個組件,分別是:ext-4.0.2a.jsb2||spket-1.6.16.jar,需要的朋友可以參考下
    2016-05-05
  • 詳解Java如何在業(yè)務(wù)代碼中優(yōu)雅的使用策略模式

    詳解Java如何在業(yè)務(wù)代碼中優(yōu)雅的使用策略模式

    這篇文章主要為大家介紹了Java如何在業(yè)務(wù)代碼中優(yōu)雅的使用策略模式,文中的示例代碼講解詳細,具有一定的學(xué)習(xí)價值,感興趣的可以了解下
    2023-08-08
  • Java RabbitMQ的TTL和DLX全面精解

    Java RabbitMQ的TTL和DLX全面精解

    過期時間TTL表示可以對消息設(shè)置預(yù)期的時間,在這個時間內(nèi)都可以被消費者接收獲??;過了之后消息將自動被刪除。DLX, 可以稱之為死信交換機,當(dāng)消息在一個隊列中變成死信之后,它能被重新發(fā)送到另一個交換機中,這個交換機就是DLX ,綁定DLX的隊列就稱之為死信隊列
    2021-09-09
  • Java上傳文件錯誤java.lang.NoSuchMethodException的解決辦法

    Java上傳文件錯誤java.lang.NoSuchMethodException的解決辦法

    今天小編就為大家分享一篇關(guān)于Java上傳文件錯誤java.lang.NoSuchMethodException的解決辦法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • Java從源碼角度解析SpringMVC執(zhí)行流程

    Java從源碼角度解析SpringMVC執(zhí)行流程

    這篇文章主要介紹了Java從源碼角度解析SpringMVC執(zhí)行流程,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • Java輸出通過InetAddress獲得的IP地址數(shù)組詳細解析

    Java輸出通過InetAddress獲得的IP地址數(shù)組詳細解析

    由于byte被認(rèn)為是unsigned byte,所以最高位的1將會被解釋為符號位,另外Java中存儲是按照補碼存儲,所以1000 0111會被認(rèn)為是補碼形式,轉(zhuǎn)換成原碼便是1111 0001,轉(zhuǎn)換成十進制數(shù)便是-121
    2013-09-09
  • MyBatis的9種動態(tài)標(biāo)簽詳解

    MyBatis的9種動態(tài)標(biāo)簽詳解

    大家好,本篇文章主要講的是MyBatis的9種動態(tài)標(biāo)簽詳解,感興趣的同學(xué)趕快來看一看吧,感興趣的同學(xué)趕快來看一看吧
    2021-12-12
  • 一個MIDP俄羅斯方塊游戲的設(shè)計和實現(xiàn)

    一個MIDP俄羅斯方塊游戲的設(shè)計和實現(xiàn)

    一個MIDP俄羅斯方塊游戲的設(shè)計和實現(xiàn)...
    2006-12-12
  • SpringBoot使用Redisson實現(xiàn)延遲執(zhí)行的完整示例

    SpringBoot使用Redisson實現(xiàn)延遲執(zhí)行的完整示例

    這篇文章主要介紹了SpringBoot使用Redisson實現(xiàn)延遲執(zhí)行的完整示例,文中通過代碼示例講解的非常詳細,對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-06-06

最新評論

枣强县| 大埔区| 南丰县| 黄大仙区| 黑河市| 凤台县| 苍梧县| 陇川县| 淮北市| 喀喇| 潢川县| 平罗县| 和硕县| 农安县| 镇原县| 常山县| 游戏| 龙州县| 汝州市| 泸溪县| 天峻县| 连城县| 安新县| 梅州市| 崇阳县| 嘉鱼县| 嘉兴市| 正安县| 云龙县| 古蔺县| 香港| 大荔县| 霍州市| 从化市| 石屏县| 聊城市| 北海市| 攀枝花市| 哈尔滨市| 信宜市| 电白县|