亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

SpringBoot整合Sa-Token實現(xiàn)?API?接口簽名安全校驗功能

 更新時間:2023年07月18日 17:24:10   作者:sco5282  
這篇文章主要介紹了SpringBoot整合Sa-Token實現(xiàn)?API?接口簽名安全校驗功能,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

在涉及跨系統(tǒng)接口調用時,我們容易碰到以下安全問題:

  • 請求身份被偽造
  • 請求參數被篡改
  • 請求被抓包,然后重放攻擊

sa-token api-sign 模塊將幫你輕松解決以上難題。(此插件是內嵌到 sa-token-core 核心包中的模塊,開發(fā)者無需再次引入其它依賴,插件直接可用)

假設我們有如下業(yè)務需求:

用戶在 A 系統(tǒng)參與活動成功后,活動獎勵以余額的形式下發(fā)到 B 系統(tǒng)。

1. 初始方案:直接裸奔

在不考慮安全問題的情況下,我們很容易完成這個需求:

1、在 B 系統(tǒng)開放一個接口

@RestController
@RequestMapping("/sign")
public class SignController {
    @PostMapping("/addMoney")
    public String addMoney(Long userId, Long money) {
        // TODO 處理業(yè)務...
        return "ADD SUCCESS";
    }
}

2、在 A 系統(tǒng)使用 http 工具類調用這個接口

@RestController
@RequestMapping("/activity")
public class ActivityController {
    @PostMapping("/join")
    public String join() {
        // 參加完活動后,發(fā)送余額
        Long userId = 1L;
        Long money = 100L;
        Map<String, Object> params = new HashMap<>();
        params.put("userId", userId);
        params.put("money", money);
        String url = "http://localhost:8079/sign/addMoney";
        String result = HttpUtil.post(url, params);
        return "join";
    }
}

上述代碼簡單的完成了需求,但是很明顯它有一個安全問題:B 系統(tǒng)開放的接口不僅可以被 A 系統(tǒng)調用,還可以被其它任何人調用,甚至別人可以本地跑一個 for 循環(huán)調用這個接口,為自己無限充值金額

2. 方案升級:增加 secretKey 校驗

為防止 B 系統(tǒng)開放的接口被陌生人任意調用,我們增加一個 secretKey 參數

@PostMapping("/addMoney")
public String addMoney(Long userId, Long money, String secretKey) {
    // 校驗 secretKey
    if (!check(secretKey)) {
        throw new RuntimeException("無效 secretKey,無法響應請求");
    }
    // TODO 處理業(yè)務...
    return "ADD SUCCESS";
}

由于 A 系統(tǒng)是我們 “自己人”,所以它可以拿著 secretKey 進行合法請求:

@PostMapping("/join")
public String join() {
    // 參加完活動后,發(fā)送余額
    Map<String, Object> params = new HashMap<>();
    params.put("userId", userId);
    params.put("money", money);
    params.put("secretKey", "×××××××××××");
    String url = "http://localhost:8079/sign/addMoney";
    String result = HttpUtil.post(url, params);
    return "join";
}

現(xiàn)在,即使 B 系統(tǒng)的接口被暴露了,也不會被陌生人任意調用了,安全性得到了一定的保證,但是仍然存在一些問題:

  • 如果請求被抓包,secretKey 就會泄露,因為每次請求都在 url 中明文傳輸了 secretKey 參數。
  • 如果請求被抓包,請求的其它參數就可以被任意修改,例如可以將 money 參數修改為 9999999,B系統(tǒng)無法確定參數是否被修改過。

3.方案再升級:使用摘要算法生成參數簽名

首先,在 A 系統(tǒng)不要直接發(fā)起請求,而是先計算一個 sign 參數:

@PostMapping("/join")
public String join() {
    // 參加完活動后,發(fā)送余額
    Long userId = 1L;
    Long money = 100L;
    String secretKey = "×××××××××××";
    // 計算 sign
    String sign = md5("money=" + money + "&userId=" + userId + "&key=" + secretKey);
    Map<String, Object> params = new HashMap<>();
    params.put("userId", userId);
    params.put("money", money);
    params.put("sign", sign);
    String url = "http://localhost:8079/sign/addMoney";
    String result = HttpUtil.post(url, params);
    return "join";
}

注意:此處計算簽名時,需要將所有參數按照字典順序依次排列(key除外,掛在最后面)

然后在 B 系統(tǒng)接收請求時,使用同樣的算法、同樣的秘鑰,生成 sign 字符串,與參數中 sign 值進行比較:

@PostMapping("/addMoney")
public String addMoney(Long userId, Long money, String sign) {
    // 在 B 系統(tǒng),使用同樣的算法、同樣的密鑰,計算出 sign2,與傳入的 sign 進行比對
    String sign2 = md5("money=" + money + "&userId=" + userId + "&key=" + secretKey);
    if (!sign2.equals(sign)) {
        return "無效 sign,無法響應請求";
    }
    // TODO 處理業(yè)務...
    return "ADD SUCCESS";
}

因為 sign 的值是由 userId、money、secretKey 三個參數共同決定的,所以只要有一個參數不一致,就會造成最終生成 sign 也是不一致的,所以,根據比對結果:

  • 如果 sign 一致,說明這是個合法請求。
  • 如果 sign 不一致,說明發(fā)起請求的客戶端秘鑰不正確,或者請求參數被篡改過,是個不合法請求。

此方案優(yōu)點:

  • 不在 url 中直接傳遞 secretKey 參數了,避免了泄露風險。
  • 由于 sign 參數的限制,請求中的參數也不可被篡改,B 系統(tǒng)可放心的使用這些參數。

此方案仍然存在以下缺陷:

  • 被抓包后,請求可以被無限重放,B 系統(tǒng)無法判斷請求是真正來自于 A 系統(tǒng)發(fā)出的,還是被抓包后重放的。
@PostMapping("/join")
public String join() {
    // 參加完活動后,發(fā)送余額
    Long userId = 1L;
    Long money = 100L;
    String nonce = SaFoxUtil.getRandomString(32); // 隨機32位字符串
    String secretKey = "×××××××××××";
    // 計算 sign
    String sign = md5("money=" + money + "&nonce=" + nonce + "&userId=" + userId + "&key=" + secretKey);
    Map<String, Object> params = new HashMap<>();
    params.put("userId", userId);
    params.put("money", money);
    params.put("nonce", nonce);
    params.put("sign", sign);
    String url = "http://localhost:8079/sign/addMoney";
    String result = HttpUtil.post(url, params);
    return "join";
}

4. 方案再再升級:追加 nonce 隨機字符串

首先,在 A 系統(tǒng)發(fā)起調用前,追加一個 nonce 參數,一起參與到簽名中:

public String join() {
   // 參加完活動后,發(fā)送余額
   Long userId = 1L;
   Long money = 100L;
   String nonce = SaFoxUtil.getRandomString(32); // 隨機32位字符串
   String secretKey = "×××××××××××";
   // 計算 sign
   String sign = md5("money=" + money + "&nonce=" + nonce + "&userId=" + userId + "&key=" + secretKey);
   Map<String, Object> params = new HashMap<>();
   params.put("userId", userId);
   params.put("money", money);
   params.put("nonce", nonce);
   params.put("sign", sign);
   String url = "http://localhost:8079/sign/addMoney";
   String result = HttpUtil.post(url, params);
   return "join";
}

然后在 B 系統(tǒng)接收請求時,也把 nonce 參數加進去生成 sign 字符串,進行比較:

public String addMoney(Long userId, Long money, String nonce,String sign) {
    // 檢查此 nonce 是否已被使用過了
    if (Objects.nonNull(CacheUtil.get("nonce_" + nonce))) {
        return "此 nonce 已被使用過了,請求無效";
    }
    // 在 B 系統(tǒng),使用同樣的算法、同樣的密鑰,計算出 sign2,與傳入的 sign 進行比對
    String sign2 = md5("money=" + money + "&nonce=" + nonce + "&userId=" + userId + "&key=" + secretKey);
    if (!sign2.equals(sign)) {
        return "無效 sign,無法響應請求";
    }
    // 存入緩存
    CacheUtil.set("nonce_" + nonce, "1");
    // TODO 處理業(yè)務...
    return "ADD SUCCESS";
}

代碼分析:

  • 為方便理解,我們先看第 3 步:此處在校驗簽名成功后,將 nonce 隨機字符串記入緩存中。
  • 再看第 1 步:每次請求進來,先查看一下緩存中是否已經記錄了這個隨機字符串,如果是,則立即返回:無效請求。

這兩步的組合,保證了一個 nonce 隨機字符串只能被使用一次,如果請求被抓包后重放,是無法通過 nonce 校驗的。

至此,問題似乎已被解決了 …… 嗎?

別急,我們還有一個問題沒有考慮:這個 nonce 在字符串在緩存應該被保存多久呢?

  • 保存 15 分鐘?那抓包的人只需要等待 15 分鐘,你的 nonce 記錄在緩存中消失,請求就可以被重放了。
  • 那保存 24 小時?保存一周?保存半個月?好像無論保存多久,都無法從根本上解決這個問題。

你可能會想到,那我永久保存吧。這樣確實能解決問題,但顯然服務器承載不了這么做,即使再微小的數據量,在時間的累加下,也總一天會超出服務器能夠承載的上限。

5. 方案再再再升級:追加 timestamp 時間戳

我們可以再追加一個 timestamp 時間戳參數,將請求的有效性限定在一個有限時間范圍內,例如 15分鐘。

首先,在 A 系統(tǒng)追加 timestamp 參數:

public String join() {
    // 參加完活動后,發(fā)送余額
    Long userId = 1L;
    Long money = 100L;
    Long timestamp = System.currentTimeMillis();
    String nonce = SaFoxUtil.getRandomString(32); // 隨機32位字符串
    String secretKey = "×××××××××××";
    // 計算 sign
    String sign = md5("money=" + money + "&nonce=" + nonce + "&timestamp=" + timestamp + "&userId=" + userId + "&key=" + secretKey);
    Map<String, Object> params = new HashMap<>();
    params.put("userId", userId);
    params.put("money", money);
    params.put("nonce", nonce);
    params.put("timestamp", timestamp);
    params.put("sign", sign);
    String url = "http://localhost:8079/sign/addMoney";
    String result = HttpUtil.post(url, params);
    return "join";
}

在 B 系統(tǒng)檢測這個 timestamp 是否超出了允許的范圍

public String addMoney(Long userId, Long money, Long timestamp, String nonce,String sign) {
    // 1、檢查 timestamp 是否超出允許的范圍(此處假定最大允許15分鐘差距)
    long timestampDisparity = System.currentTimeMillis() - timestamp; // 實際的時間差
    if(timestampDisparity > 1000 * 60 * 15) {
        return "timestamp 時間差超出允許的范圍,請求無效";
    }
    // 檢查此 nonce 是否已被使用過了
    if (Objects.nonNull(CacheUtil.get("nonce_" + nonce))) {
        return "此 nonce 已被使用過了,請求無效";
    }
    // 在 B 系統(tǒng),使用同樣的算法、同樣的密鑰,計算出 sign2,與傳入的 sign 進行比對
    String sign2 = md5("money=" + money + "&nonce=" + nonce + "&userId=" + userId + "&key=" + secretKey);
    if (!sign2.equals(sign)) {
        return "無效 sign,無法響應請求";
    }
    // 將 nonce 記入緩存,ttl 有效期和 allowDisparity 允許時間差一致 
    CacheUtil.set("nonce_" + nonce, "1", 1000 * 60 * 15);
    // TODO 處理業(yè)務...
    return "ADD SUCCESS";
}

至此,抓包者:

  • 如果在 15 分鐘內重放攻擊,nonce 參數不答應:緩存中可以查出 nonce 值,直接拒絕響應請求。
  • 如果在 15 分鐘后重放攻擊,timestamp 參數不答應:超出了允許的 timestamp 時間差,直接拒絕響應請求。

6. 服務器的時鐘差異造成安全問題

以上的代碼,均假設 A 系統(tǒng)服務器與 B 系統(tǒng)服務器的時鐘一致,才可以正常完成安全校驗,但在實際的開發(fā)場景中,有些服務器會存在時鐘不準確的問題。

假設 A 服務器與 B 服務器的時鐘差異為 10 分鐘,即:在 A 服務器為 8:00 的時候,B 服務器為 7:50。

  • A 系統(tǒng)發(fā)起請求,其生成的時間戳也是代表 8:00。
  • B 系統(tǒng)接受到請求后,完成業(yè)務處理,此時 nonce 的 ttl 為 15分鐘,到期時間為 7:50 + 15分 = 8:05。
  • 8.05 后,nonce 緩存消失,抓包者重放請求攻擊:
    • timestamp 校驗通過:因為時間戳差距僅有 8.05 - 8.00 = 5分鐘,小于 15 分鐘,校驗通過。
    • -nonce 校驗通過:因為此時 nonce 緩存已經消失,可以通過校驗。
    • sign 校驗通過:因為這本來就是由 A 系統(tǒng)構建的一個合法簽名。

攻擊完成。

要解決上述問題,有兩種方案:

  • 方案一:修改服務器時鐘,使兩個服務器時鐘保持一致。
  • 方案二:在代碼層面兼容時鐘不一致的場景。

要采用方案一的同學可自行搜索一下同步時鐘的方法,在此暫不贅述,此處詳細闡述一下方案二。

我們只需簡單修改一下,B 系統(tǒng)校驗參數的代碼即可:

public String addMoney(Long userId, Long money, Long timestamp, String nonce,String sign) {
    // 1、檢查 timestamp 是否超出允許的范圍 (重點一:此處需要取絕對值)
    long timestampDisparity = Math.abs(System.currentTimeMillis() - timestamp);
    if(timestampDisparity > 1000 * 60 * 15) {
        return "timestamp 時間差超出允許的范圍,請求無效";
    }
    // 檢查此 nonce 是否已被使用過了
    if (Objects.nonNull(CacheUtil.get("nonce_" + nonce))) {
        return "此 nonce 已被使用過了,請求無效";
    }
    // 在 B 系統(tǒng),使用同樣的算法、同樣的密鑰,計算出 sign2,與傳入的 sign 進行比對
    String sign2 = md5("money=" + money + "&nonce=" + nonce + "&userId=" + userId + "&key=" + secretKey);
    if (!sign2.equals(sign)) {
        return "無效 sign,無法響應請求";
    }
    // 將 nonce 記入緩存,防止重復使用(重點二:此處需要將 ttl 設定為允許 timestamp 時間差的值 x 2 )
    CacheUtil.set("nonce_" + nonce, "1", (1000 * 60 * 15) * 2;
    // TODO 處理業(yè)務...
    return "ADD SUCCESS";
}

7. 使用 Sa-Token 框架完成 API 參數簽名

接下來步入正題,使用 Sa-Token 內置的 sign 模塊,方便的完成 API 簽名創(chuàng)建、校驗等步驟:

  • 不限制請求的參數數量,方便組織業(yè)務需求代碼。
  • 自動補全 nonce、timestamp 參數,省時省力。
  • 自動構建簽名,并序列化參數為字符串。
  • 一句代碼完成 nonce、timestamp、sign 的校驗,防偽造請求調用、防參數篡改、防重放攻擊。

7.1 引入依賴

api-sign 模塊已內嵌到核心包,只需要引入 sa-token 本身依賴即可:(請求發(fā)起端和接收端都需要引入)

<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.35.0.RC</version>
</dependency>

7.2 配置密鑰

請求發(fā)起端和接收端需要配置一個相同的秘鑰,在 application.yml 中配置:

sa-token: 
    sign:
        # API 接口簽名秘鑰 (隨便亂摁幾個字母即可)
        secret-key: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor

7.3 請求發(fā)起端構建簽名

public String join() {
    // 參加完活動后,發(fā)送余額
    Long userId = 1L;
    Long money = 100L;
    Map<String, Object> params = new HashMap<>();
    params.put("userId", userId);
    params.put("money", money);
    SaSignUtil.addSignParamsAndJoin(params);
    String url = "http://localhost:8079/sign/addMoney";
    return HttpUtil.post(url, params);
}

7.4 請求接受端校驗簽名

public String addMoney(Long userId, Long money) {
    // 1、校驗請求中的簽名
    SaSignUtil.checkRequest(SaHolder.getRequest());
    // 2、校驗通過,處理業(yè)務
    System.out.println("userId=" + userId);
    System.out.println("money=" + money);
    return "ADD SUCCESS";
}

如上代碼便可簡單方便的完成 API 接口參數簽名校驗,當請求端的秘鑰不對,或者請求參數被篡改、請求被重放時,均無法通過 SaSignUtil.checkRequest 校驗

7.5 原理分析

7.5.1 構建簽名

SaSignUtil#addSignParamsAndJoin(params);

public static String addSignParamsAndJoin(Map<String, Object> paramsMap) {
    return SaManager.getSaSignTemplate().addSignParamsAndJoin(paramsMap);
}

會調用 SaSignTemplate 類中的方法

SaSignTemplate#addSignParamsAndJoin() 方法

public String addSignParamsAndJoin(Map<String, Object> paramsMap) {
	// 1.添加參數:timestamp、nonce、sign
    paramsMap = this.addSignParams(paramsMap);
    // 2.將 map 使用 & 轉化為String
    return this.joinParams(paramsMap);
}

這個方法有兩個邏輯:

  1. 添加參數:timestamp、nonce、sign
  2. 將 map 使用 & 轉化為String

SaSignTemplate#addSignParams() 方法

public Map<String, Object> addSignParams(Map<String, Object> paramsMap) {
    paramsMap.put(timestamp, String.valueOf(System.currentTimeMillis()));
    paramsMap.put(nonce, SaFoxUtil.getRandomString(32));
    paramsMap.put(sign, this.createSign(paramsMap));
    return paramsMap;
}

SaSignTemplate#createSign() 方法:生成簽名

public String createSign(Map<String, ?> paramsMap) {
    String secretKey = this.getSecretKey();
    SaSignException.throwByNull(secretKey, "參與參數簽名的秘鑰不可為空", 12201);
    if (((Map)paramsMap).containsKey(sign)) {
        paramsMap = new TreeMap((Map)paramsMap);
        ((Map)paramsMap).remove(sign);
    }
	// 按照數據字典進行排序,并將 map 使用 & 轉化為String
    String paramsStr = this.joinParamsDictSort((Map)paramsMap);
    String fullStr = paramsStr + "&" + key + "=" + secretKey;
    // md5
    return this.abstractStr(fullStr);
}
public String abstractStr(String fullStr) {
    return SaSecureUtil.md5(fullStr);
}

這個方法有兩個邏輯:

  • 按照數據字典進行排序,并將 map 使用 & 轉化為String
  • 使用 md5 摘要算法

7.5.2 驗證簽名

SaSignUtil.checkRequest(SaHolder.getRequest());

public static void checkRequest(SaRequest request) {
    SaManager.getSaSignTemplate().checkRequest(request);
}

還是會調用 SaSignTemplate 類中的方法

SaSignTemplate#checkParamMap() 方法:校驗請求參數

public void checkRequest(SaRequest request) {
    this.checkParamMap(request.getParamMap());
}
public void checkParamMap(Map<String, String> paramMap) {
    String timestampValue = (String)paramMap.get(timestamp);
    String nonceValue = (String)paramMap.get(nonce);
    String signValue = (String)paramMap.get(sign);
    // 1.校驗時間戳
    this.checkTimestamp(Long.parseLong(timestampValue));
    // 2.校驗隨機數
    if (this.getSignConfigOrGlobal().getIsCheckNonce()) {
        this.checkNonce(nonceValue);
    }
	// 3.校驗簽名
    this.checkSign(paramMap, signValue);
}

這個方法有三個邏輯:

  • 校驗時間戳:判斷是否在時間差范圍內
  • 校驗隨機數:判斷此隨機數是否已使用
  • 校驗簽名:判斷原簽名和現(xiàn)在生成的簽名是否一致

SaSignTemplate#checkNonce() 方法:校驗隨機數

public void checkNonce(String nonce) {
    if (SaFoxUtil.isEmpty(nonce)) {
        throw new SaSignException("nonce 為空,無效");
    } else {
        String key = this.splicingNonceSaveKey(nonce);
        if (SaManager.getSaTokenDao().get(key) != null) {
            throw new SaSignException("此 nonce 已被使用過,不可重復使用:" + nonce);
        } else {
            SaManager.getSaTokenDao().set(key, nonce, this.getSignConfigOrGlobal().getSaveNonceExpire() * 2L + 2L);
        }
    }
}

SaToken 存儲

SaTokenDao 是存儲接口,默認實現(xiàn)是用的是 SaTokenDaoDefaultImpl。SaTokenDaoDefaultImpl 存儲數據,主要是通過 ConcurrentHashMap 存放在本地內存中。

SaManager#getSaTokenDao() 方法:

public static SaTokenDao getSaTokenDao() {
    if (saTokenDao == null) {
        Class var0 = SaManager.class;
        synchronized(SaManager.class) {
            if (saTokenDao == null) {
                setSaTokenDaoMethod(new SaTokenDaoDefaultImpl());
            }
        }
    }
    return saTokenDao;
}

SaTokenDaoDefaultImpl

public class SaTokenDaoDefaultImpl implements SaTokenDao {
	// 數據集合 
    public Map<String, Object> dataMap = new ConcurrentHashMap();
    // 過期時間集合 (單位: 毫秒) , 記錄所有key的到期時間 [注意不是剩余存活時間] 
    public Map<String, Long> expireMap = new ConcurrentHashMap();
    public Thread refreshThread;
    public volatile boolean refreshFlag;
    public SaTokenDaoDefaultImpl() {
    	// 定時清理過期數據
        this.initRefreshThread();
    }
    public String get(String key) {
        this.clearKeyByTimeout(key);
        return (String)this.dataMap.get(key);
    }
    public void set(String key, String value, long timeout) {
        if (timeout != 0L && timeout > -2L) {
            this.dataMap.put(key, value);
            this.expireMap.put(key, timeout == -1L ? -1L : System.currentTimeMillis() + timeout * 1000L);
        }
    }
	public void initRefreshThread() {
        if (SaManager.getConfig().getDataRefreshPeriod() > 0) {
            this.refreshFlag = true;
            this.refreshThread = new Thread(() -> {
                while(true) {
                    try {
                        try {
                            if (!this.refreshFlag) {
                                return;
                            }
                            this.refreshDataMap();
                        } catch (Exception var2) {
                            var2.printStackTrace();
                        }
                        int dataRefreshPeriod = SaManager.getConfig().getDataRefreshPeriod();
                        if (dataRefreshPeriod <= 0) {
                            dataRefreshPeriod = 1;
                        }
                        Thread.sleep((long)dataRefreshPeriod * 1000L);
                    } catch (Exception var3) {
                        var3.printStackTrace();
                    }
                }
            });
            this.refreshThread.start();
        }
    }
}

如果僅僅存放在本地內存中,涉及到多個項目,可能數據無法共享。

引入倉庫 sa-token-dao-redis-jackson

<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-redis-jackson</artifactId>
    <version>1.35.0.RC</version>
</dependency>
<!-- 提供Redis連接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

SaTokenDaoRedisJackson 使用 Redis 作為存儲數據的地方

SaBeanInject#setSaTokenDao,SaBeanInject 是自動配置的。當系統(tǒng)中存在 SaTokenDao的 Bean 實例,則設置SaTokenDao 實例

public class SaBeanInject {
    @Autowired(
        required = false
    )
    public void setSaTokenDao(SaTokenDao saTokenDao) {
        SaManager.setSaTokenDao(saTokenDao);
    }
}

參考:

API 接口參數簽名

【開源項目】使用Sa-Token框架完成API參數簽名

到此這篇關于SpringBoot整合Sa-Token 快速實現(xiàn) API 接口簽名安全校驗的文章就介紹到這了,更多相關SpringBoot API 接口簽名安全校驗內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • SpringMVC4+MyBatis+SQL Server2014實現(xiàn)數據庫讀寫分離

    SpringMVC4+MyBatis+SQL Server2014實現(xiàn)數據庫讀寫分離

    這篇文章主要介紹了SpringMVC4+MyBatis+SQL Server2014實現(xiàn)讀寫分離,需要的朋友可以參考下
    2017-04-04
  • java hasNextInt判斷是否為數字的方法

    java hasNextInt判斷是否為數字的方法

    今天小編就為大家分享一篇java hasNextInt判斷是否為數字的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-07-07
  • Spring?Security用戶定義?

    Spring?Security用戶定義?

    這篇文章主要介紹了Spring?Security用戶定義,大家都知道?Spring?Security的用戶定義有很多方式,其實主要有兩種,基于內存的和基于數據庫的,下面我給大家簡單介紹一下這兩種方式,需要的朋友可以參考下
    2022-02-02
  • HTTPClient如何在Springboot中封裝工具類

    HTTPClient如何在Springboot中封裝工具類

    這篇文章主要介紹了HTTPClient如何在Springboot中封裝工具類問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Java連接Hbase的項目實踐

    Java連接Hbase的項目實踐

    HBase是基于Hadoop的高可靠、高性能、面向列的NoSQL數據庫,它提供了對海量數據的存儲和實時讀寫的能力,本文主要介紹了Java連接Hbase的項目實踐,具有一定的參考價值,感興趣的可以了解一下
    2024-01-01
  • 關于Java中String創(chuàng)建的字符串對象內存分配測試問題

    關于Java中String創(chuàng)建的字符串對象內存分配測試問題

    這篇文章主要介紹了Java中String創(chuàng)建的字符串對象內存分配測試,給大家詳細介紹了在創(chuàng)建String對象的兩種常用方法比較,通過示例代碼給大家介紹的非常詳細,需要的朋友可以參考下
    2021-07-07
  • java向es中寫入數據報錯org.elasticsearch.action.ActionReque問題

    java向es中寫入數據報錯org.elasticsearch.action.ActionReque問題

    這篇文章主要介紹了java向es中寫入數據報錯org.elasticsearch.action.ActionReque問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • 淺析Java Scanner 類的用法

    淺析Java Scanner 類的用法

    這篇文章主要介紹了Java Scanner 類的用法,文中講解非常詳細,代碼幫助大家更好的理解和學習,感興趣的朋友可以了解下
    2020-07-07
  • JavaWeb開發(fā)入門第二篇Tomcat服務器配置講解

    JavaWeb開發(fā)入門第二篇Tomcat服務器配置講解

    JavaWeb開發(fā)入門第二篇主要介紹了Tomcat服務器配置的方法教大家如何使用Tomcat服務器,感興趣的小伙伴們可以參考一下
    2016-04-04
  • Java日常練習題,每天進步一點點(24)

    Java日常練習題,每天進步一點點(24)

    下面小編就為大家?guī)硪黄狫ava基礎的幾道練習題(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你
    2021-07-07

最新評論