主頁 > 後端開發 > Java實作AWS S3 V4 Authorization自定義驗證

Java實作AWS S3 V4 Authorization自定義驗證

2023-06-05 07:51:17 後端開發

前言

最近在開發檔案存盤服務,需要符合s3的協議標準,可以直接接入aws-sdk,本文針對sdk發出請求的鑒權資訊進行重新組合再簽名驗證有效性,sdk版本如下

        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>s3</artifactId>
            <version>2.20.45</version>
        </dependency>

演算法決議

首先對V4版本簽名演算法的資料結構及簽名流程進行拆解分析,以請求頭簽名為示例講解

signature = doSign(waitSignString)

簽名示例

請求頭簽名

AWS4-HMAC-SHA256 Credential=admin/20230530/us-east-1/s3/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date, Signature=6f50628a101b46264c7783937be0366762683e0d319830b1844643e40b3b0ed

Url簽名

http://localhost:8001/s3/kkk/test.docx?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230531T024715Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=admin%2F20230531%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=038e2ea71073761aa0370215621599649e9228177c332a0a79f784b1a6d9ee39

資料結構

waitSignString = doHex(【第一部分】+【第二部分】+【第三部分】+【第四部分】),每部分使用\n換行符連接,第四部分不要加上換行符

第一部分

Algorithm – 用于創建規范請求的哈希的演算法,對于 SHA-256,演算法是 AWS4-HMAC-SHA256,則這部分的內容固定為

"AWS4-HMAC-SHA256" + "\n"

第二部分

RequestDateTime – 在憑證范圍內使用的日期和時間,這個時間為請求發出的時間,直接從請求頭獲取x-amz-date即可,這部分內容為

request.getHeader("x-amz-date") + "\n"

第三部分

CredentialScope – 憑證范圍,這會將生成的簽名限制在指定的區域和服務范圍內,該字串采用以下格式:YYYYMMDD/region/service/aws4_request

這部分由4個內容資訊拼接組成

  • 請求時間的YYYYMMDD格式
  • 存盤區域
  • 存盤服務
  • 請求頭

這些資訊我們都可以從請求頭的Authorization憑證提取出Credential部分進行拆分重新組合

        String[] parts = authorization.trim().split("\\,");
        String credential = parts[0].split("\\=")[1];
        String[] credentials = credential.split("\\/");
        String accessKey = credentials[0];
        if (!accessKeyId.equals(accessKey)) {
            return false;
        }
        String date = credentials[1];
        String region = credentials[2];
        String service = credentials[3];
        String aws4Request = credentials[4];

這部分內容為

date + "/" + region + "/" + service + "/" + aws4Request + "\n"

第四部分

HashedCanonicalRequest – 規范請求的哈希

這部分內容為

doHex(canonicalRequest)

canonicalRequest具體拆解又可以6小部分組成,每部分使用\n換行符連接,最后不要加上換行符

<HTTPMethod>\n
<CanonicalURI>\n
<CanonicalQueryString>\n
CanonicalHeaders>\n
<SignedHeaders>\n
<HashedPayload>
  • HTTPMethod

    代表請求的HTTP方法,例如GET,POST,DELETE,PUT等,直接從request獲取即可

    這部分內容為

    String HTTPMethod = request.getMethod() + "\n"
    
  • CanonicalURI

    代表請求的路由部分,例如完成請求為http://localhost:8001/s3/aaaa/ccc.txt,則該部分為/s3/aaaa/ccc.txt

    需要進行encode操作,我這里直接獲取則省略了這部分

    這部分內容為

    String CanonicalURI = request.getRequestURI().split("\\?")[0] + "\n";
    
  • CanonicalQueryString

    代表請求引數的拼接成字串key1=value1&key2=value2這種形式,拼接的key需要按照字母排序

    value需要進行encode操作,我這里直接獲取則省略了這部分

            String queryString = ConvertOp.convert2String(request.getQueryString());
            if(!StringUtil.isEmpty(queryString)){
                Map<String, String> queryStringMap =  parseQueryParams(queryString);
                List<String> keyList = new ArrayList<>(queryStringMap.keySet());
                Collections.sort(keyList);
                StringBuilder queryStringBuilder = new StringBuilder("");
                for (String key:keyList) {
                    queryStringBuilder.append(key).append("=").append(queryStringMap.get(key)).append("&");
                }
                queryStringBuilder.deleteCharAt(queryStringBuilder.lastIndexOf("&"));
            }
    
        public static Map<String, String> parseQueryParams(String queryString) {
            Map<String, String> queryParams = new HashMap<>();
            try {
                if (queryString != null && !queryString.isEmpty()) {
                    String[] queryParamsArray = queryString.split("\\&");
    
                    for (String param : queryParamsArray) {
                        String[] keyValue = https://www.cnblogs.com/yanpeng19940119/archive/2023/06/04/param.split("\\=");
                        if (keyValue.length == 1) {
                            String key = keyValue[0];
                            String valuehttps://www.cnblogs.com/yanpeng19940119/archive/2023/06/04/= "";
                            queryParams.put(key, value);
                        }
                        else if (keyValue.length == 2) {
                            String key = keyValue[0];
                            String value = https://www.cnblogs.com/yanpeng19940119/archive/2023/06/04/keyValue[1];
                            queryParams.put(key, value);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return queryParams;
        }
    

    這部分內容為

    String CanonicalQueryString = queryStringBuilder.toString() + "\n"
    
  • CanonicalHeaders

    代表請求頭拼接成字串key:value的形式,每個head部分使用\n換行符連接,拼接的key需要按照字母排序

    簽名的請求頭從Authorization決議獲取

            String signedHeader = parts[1].split("\\=")[1];
            String[] signedHeaders = signedHeader.split("\\;");
    
            String headString = "";
            for (String name : signedHeaders) {
                headString += name + ":" + request.getHeader(name) + "\n";
            }
    

    這部分內容為

    String CanonicalHeaders = headString + "\n"
    
  • SignedHeaders

    代表請求頭的key部分,使用;隔開

    這部分內容為從Authorization決議中獲取

    這部分內容為

    String SignedHeaders = signedHeader + "\n"
    
  • HashedPayload

    代表請求body部分的簽名,直接從requet的head提取x-amz-content-sha256內容

    這部分內容為

    String HashedPayload = Stringrequest.getHeader("x-amz-content-sha256")
    

doHex

本部分只是一個字串轉16進制的一個操作

    private String doHex(String data) {
        MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(data.getBytes("UTF-8"));
            byte[] digest = messageDigest.digest();
            return String.format("%064x", new java.math.BigInteger(1, digest));
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

簽名流程

doSign 的流程為doBytesToHex(doHmacSHA256(signatureKey,waitSignString ))

doBytesToHex為byte轉16進制操作

    private String doBytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars).toLowerCase();
    }

doHmacSHA256為簽名演算法

    private byte[] doHmacSHA256(byte[] key, String data) throws Exception {
        String algorithm = "HmacSHA256";
        Mac mac = Mac.getInstance(algorithm);
        mac.init(new SecretKeySpec(key, algorithm));
        return mac.doFinal(data.getBytes("UTF8"));
    }

signatureKey簽名密鑰由secretAccessKey,請求時間,存盤區域,存盤服務,請求頭這5個要素進行疊加簽名生成

        byte[] kSecret = ("AWS4" + secretAccessKey).getBytes("UTF8");
        byte[] kDate = doHmacSHA256(kSecret, date);
        byte[] kRegion = doHmacSHA256(kDate, region);
        byte[] kService = doHmacSHA256(kRegion, service);
        byte[] signatureKey = doHmacSHA256(kService, aws4Request);

將最終生成的再簽名與Authorization中決議出的Signature進行比較,一致則鑒權成功

除錯位置

除錯程序中需要驗證每部分的簽名是否拼接編碼正確,我們需要和sdk生成的內容進行比對找出問題

除錯software.amazon.awssdk.auth.signer.internal包下AbstractAws4Signer類的doSign類,獲取stringToSign與你待簽名字串比對差異,原始碼如下

    protected Builder doSign(SdkHttpFullRequest request, Aws4SignerRequestParams requestParams, T signingParams, ContentChecksum contentChecksum) {
        Builder mutableRequest = request.toBuilder();
        AwsCredentials sanitizedCredentials = this.sanitizeCredentials(signingParams.awsCredentials());
        if (sanitizedCredentials instanceof AwsSessionCredentials) {
            this.addSessionCredentials(mutableRequest, (AwsSessionCredentials)sanitizedCredentials);
        }

        this.addHostHeader(mutableRequest);
        this.addDateHeader(mutableRequest, requestParams.getFormattedRequestSigningDateTime());
        mutableRequest.firstMatchingHeader("x-amz-content-sha256").filter((h) -> {
            return h.equals("required");
        }).ifPresent((h) -> {
            mutableRequest.putHeader("x-amz-content-sha256", contentChecksum.contentHash());
        });
        this.putChecksumHeader(signingParams.checksumParams(), contentChecksum.contentFlexibleChecksum(), mutableRequest, contentChecksum.contentHash());
        AbstractAws4Signer.CanonicalRequest canonicalRequest = this.createCanonicalRequest(request, mutableRequest, contentChecksum.contentHash(), signingParams.doubleUrlEncode(), signingParams.normalizePath());
        String canonicalRequestString = canonicalRequest.string();
        String stringToSign = this.createStringToSign(canonicalRequestString, requestParams);
        byte[] signingKey = this.deriveSigningKey(sanitizedCredentials, requestParams);
        byte[] signature = this.computeSignature(stringToSign, signingKey);
        mutableRequest.putHeader("Authorization", this.buildAuthorizationHeader(signature, sanitizedCredentials, requestParams, canonicalRequest));
        this.processRequestPayload(mutableRequest, signature, signingKey, requestParams, signingParams, contentChecksum.contentFlexibleChecksum());
        return mutableRequest;
    }

代碼示例

通過攔截器進行驗證的程序,完整代碼如下,兼容了普通請求的頭部驗證和檔案下載url的簽名驗證

@Component
public class S3Intecept implements HandlerInterceptor {
    @Autowired
    private SystemConfig systemConfig;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        boolean flag = false;
        String authorization = request.getHeader("Authorization");
        if(!StringUtil.isEmpty(authorization)){
            flag = validAuthorizationHead(request, systemConfig.getUsername(), systemConfig.getPassword());
        }else{
            authorization = request.getParameter("X-Amz-Credential");
            if(!StringUtil.isEmpty(authorization)){
                flag = validAuthorizationUrl(request, systemConfig.getUsername(), systemConfig.getPassword());
            }
        }
        if(!flag){
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
        }
        return flag;
    }

    public boolean validAuthorizationHead(HttpServletRequest request, String accessKeyId, String secretAccessKey) throws Exception {
        String authorization = request.getHeader("Authorization");
        String requestDate = request.getHeader("x-amz-date");
        String contentHash = request.getHeader("x-amz-content-sha256");
        String httpMethod = request.getMethod();
        String uri = request.getRequestURI().split("\\?")[0];
        String queryString = ConvertOp.convert2String(request.getQueryString());
        //示例
        //AWS4-HMAC-SHA256 Credential=admin/20230530/us-east-1/s3/aws4_request, SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date, Signature=6f50628a101b46264c7783937be0366762683e0d319830b1844643e40b3b0ed

        ///region authorization拆分
        String[] parts = authorization.trim().split("\\,");
        //第一部分-憑證范圍
        String credential = parts[0].split("\\=")[1];
        String[] credentials = credential.split("\\/");
        String accessKey = credentials[0];
        if (!accessKeyId.equals(accessKey)) {
            return false;
        }
        String date = credentials[1];
        String region = credentials[2];
        String service = credentials[3];
        String aws4Request = credentials[4];
        //第二部分-簽名頭中包含哪些欄位
        String signedHeader = parts[1].split("\\=")[1];
        String[] signedHeaders = signedHeader.split("\\;");
        //第三部分-生成的簽名
        String signature = parts[2].split("\\=")[1];
        ///endregion

        ///region 待簽名字串
        String stringToSign = "";
        //簽名由4部分組成
        //1-Algorithm – 用于創建規范請求的哈希的演算法,對于 SHA-256,演算法是 AWS4-HMAC-SHA256,
        stringToSign += "AWS4-HMAC-SHA256" + "\n";
        //2-RequestDateTime – 在憑證范圍內使用的日期和時間,
        stringToSign += requestDate + "\n";
        //3-CredentialScope – 憑證范圍,這會將生成的簽名限制在指定的區域和服務范圍內,該字串采用以下格式:YYYYMMDD/region/service/aws4_request
        stringToSign += date + "/" + region + "/" + service + "/" + aws4Request + "\n";
        //4-HashedCanonicalRequest – 規范請求的哈希,
        //<HTTPMethod>\n
        //<CanonicalURI>\n
        //<CanonicalQueryString>\n
        //<CanonicalHeaders>\n
        //<SignedHeaders>\n
        //<HashedPayload>
        String hashedCanonicalRequest = "";
        //4.1-HTTP Method
        hashedCanonicalRequest += httpMethod + "\n";
        //4.2-Canonical URI
        hashedCanonicalRequest += uri + "\n";
        //4.3-Canonical Query String
        if(!StringUtil.isEmpty(queryString)){
            Map<String, String> queryStringMap =  parseQueryParams(queryString);
            List<String> keyList = new ArrayList<>(queryStringMap.keySet());
            Collections.sort(keyList);
            StringBuilder queryStringBuilder = new StringBuilder("");
            for (String key:keyList) {
                queryStringBuilder.append(key).append("=").append(queryStringMap.get(key)).append("&");
            }
            queryStringBuilder.deleteCharAt(queryStringBuilder.lastIndexOf("&"));

            hashedCanonicalRequest += queryStringBuilder.toString() + "\n";
        }else{
            hashedCanonicalRequest += queryString + "\n";
        }
        //4.4-Canonical Headers
        for (String name : signedHeaders) {
            hashedCanonicalRequest += name + ":" + request.getHeader(name) + "\n";
        }
        hashedCanonicalRequest += "\n";
        //4.5-Signed Headers
        hashedCanonicalRequest += signedHeader + "\n";
        //4.6-Hashed Payload
        hashedCanonicalRequest += contentHash;
        stringToSign += doHex(hashedCanonicalRequest);
        ///endregion

        ///region 重新生成簽名
        //計算簽名的key
        byte[] kSecret = ("AWS4" + secretAccessKey).getBytes("UTF8");
        byte[] kDate = doHmacSHA256(kSecret, date);
        byte[] kRegion = doHmacSHA256(kDate, region);
        byte[] kService = doHmacSHA256(kRegion, service);
        byte[] signatureKey = doHmacSHA256(kService, aws4Request);
        //計算簽名
        byte[] authSignature = doHmacSHA256(signatureKey, stringToSign);
        //對簽名編碼處理
        String strHexSignature = doBytesToHex(authSignature);
        ///endregion

        if (signature.equals(strHexSignature)) {
            return true;
        }
        return false;
    }

    public boolean validAuthorizationUrl(HttpServletRequest request, String accessKeyId, String secretAccessKey) throws Exception {
        String requestDate = request.getParameter("X-Amz-Date");
        String contentHash = "UNSIGNED-PAYLOAD";
        String httpMethod = request.getMethod();
        String uri = request.getRequestURI().split("\\?")[0];
        String queryString = ConvertOp.convert2String(request.getQueryString());
        //示例
        //"http://localhost:8001/s3/kkk/%E6%B1%9F%E5%AE%81%E8%B4%A2%E6%94%BF%E5%B1%80%E9%A1%B9%E7%9B%AE%E5%AF%B9%E6%8E%A5%E6%96%87%E6%A1%A3.docx?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230531T024715Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=admin%2F20230531%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=038e2ea71073761aa0370215621599649e9228177c332a0a79f784b1a6d9ee39

        ///region 引數準備
        //第一部分-憑證范圍
        String credential =request.getParameter("X-Amz-Credential");
        String[] credentials = credential.split("\\/");
        String accessKey = credentials[0];
        if (!accessKeyId.equals(accessKey)) {
            return false;
        }
        String date = credentials[1];
        String region = credentials[2];
        String service = credentials[3];
        String aws4Request = credentials[4];
        //第二部分-簽名頭中包含哪些欄位
        String signedHeader = request.getParameter("X-Amz-SignedHeaders");
        String[] signedHeaders = signedHeader.split("\\;");
        //第三部分-生成的簽名
        String signature = request.getParameter("X-Amz-Signature");
        ///endregion

        ///region 驗證expire
        String expires = request.getParameter("X-Amz-Expires");
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'");
        LocalDateTime startDate = LocalDateTime.parse(requestDate,formatter);
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime localDateTime = startDate.atZone(ZoneId.of("UTC")).withZoneSameInstant(zoneId);
        startDate = localDateTime.toLocalDateTime();
        LocalDateTime endDate = startDate.plusSeconds(ConvertOp.convert2Int(expires));
        if(endDate.isBefore(LocalDateTime.now())){
            return false;
        }
        ///endregion

        ///region 待簽名字串
        String stringToSign = "";
        //簽名由4部分組成
        //1-Algorithm – 用于創建規范請求的哈希的演算法,對于 SHA-256,演算法是 AWS4-HMAC-SHA256,
        stringToSign += "AWS4-HMAC-SHA256" + "\n";
        //2-RequestDateTime – 在憑證范圍內使用的日期和時間,
        stringToSign += requestDate + "\n";
        //3-CredentialScope – 憑證范圍,這會將生成的簽名限制在指定的區域和服務范圍內,該字串采用以下格式:YYYYMMDD/region/service/aws4_request
        stringToSign += date + "/" + region + "/" + service + "/" + aws4Request + "\n";
        //4-HashedCanonicalRequest – 規范請求的哈希,
        //<HTTPMethod>\n
        //<CanonicalURI>\n
        //<CanonicalQueryString>\n
        //<CanonicalHeaders>\n
        //<SignedHeaders>\n
        //<HashedPayload>
        String hashedCanonicalRequest = "";
        //4.1-HTTP Method
        hashedCanonicalRequest += httpMethod + "\n";
        //4.2-Canonical URI
        hashedCanonicalRequest += uri + "\n";
        //4.3-Canonical Query String
        if(!StringUtil.isEmpty(queryString)){
            Map<String, String> queryStringMap =  parseQueryParams(queryString);
            List<String> keyList = new ArrayList<>(queryStringMap.keySet());
            Collections.sort(keyList);
            StringBuilder queryStringBuilder = new StringBuilder("");
            for (String key:keyList) {
                if(!key.equals("X-Amz-Signature")){
                    queryStringBuilder.append(key).append("=").append(queryStringMap.get(key)).append("&");
                }
            }
            queryStringBuilder.deleteCharAt(queryStringBuilder.lastIndexOf("&"));

            hashedCanonicalRequest += queryStringBuilder.toString() + "\n";
        }else{
            hashedCanonicalRequest += queryString + "\n";
        }
        //4.4-Canonical Headers
        for (String name : signedHeaders) {
            hashedCanonicalRequest += name + ":" + request.getHeader(name) + "\n";
        }
        hashedCanonicalRequest += "\n";
        //4.5-Signed Headers
        hashedCanonicalRequest += signedHeader + "\n";
        //4.6-Hashed Payload
        hashedCanonicalRequest += contentHash;
        stringToSign += doHex(hashedCanonicalRequest);
        ///endregion

        ///region 重新生成簽名
        //計算簽名的key
        byte[] kSecret = ("AWS4" + secretAccessKey).getBytes("UTF8");
        byte[] kDate = doHmacSHA256(kSecret, date);
        byte[] kRegion = doHmacSHA256(kDate, region);
        byte[] kService = doHmacSHA256(kRegion, service);
        byte[] signatureKey = doHmacSHA256(kService, aws4Request);
        //計算簽名
        byte[] authSignature = doHmacSHA256(signatureKey, stringToSign);
        //對簽名編碼處理
        String strHexSignature = doBytesToHex(authSignature);
        ///endregion

        if (signature.equals(strHexSignature)) {
            return true;
        }
        return false;
    }

    private String doHex(String data) {
        MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(data.getBytes("UTF-8"));
            byte[] digest = messageDigest.digest();
            return String.format("%064x", new java.math.BigInteger(1, digest));
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    private byte[] doHmacSHA256(byte[] key, String data) throws Exception {
        String algorithm = "HmacSHA256";
        Mac mac = Mac.getInstance(algorithm);
        mac.init(new SecretKeySpec(key, algorithm));
        return mac.doFinal(data.getBytes("UTF8"));
    }

    final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();

    private String doBytesToHex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = hexArray[v >>> 4];
            hexChars[j * 2 + 1] = hexArray[v & 0x0F];
        }
        return new String(hexChars).toLowerCase();
    }

    public static Map<String, String> parseQueryParams(String queryString) {
        Map<String, String> queryParams = new HashMap<>();
        try {
            if (queryString != null && !queryString.isEmpty()) {
                String[] queryParamsArray = queryString.split("\\&");

                for (String param : queryParamsArray) {
                    String[] keyValue = https://www.cnblogs.com/yanpeng19940119/archive/2023/06/04/param.split("\\=");
                    if (keyValue.length == 1) {
                        String key = keyValue[0];
                        String valuehttps://www.cnblogs.com/yanpeng19940119/archive/2023/06/04/= "";
                        queryParams.put(key, value);
                    }
                    else if (keyValue.length == 2) {
                        String key = keyValue[0];
                        String value = https://www.cnblogs.com/yanpeng19940119/archive/2023/06/04/keyValue[1];
                        queryParams.put(key, value);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return queryParams;
    }

}

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/554260.html

標籤:其他

上一篇:Rust Web 全堆疊開發之撰寫 WebAssembly 應用

下一篇:返回列表

標籤雲
其他(160313) Python(38201) JavaScript(25475) Java(18185) C(15236) 區塊鏈(8269) C#(7972) AI(7469) 爪哇(7425) MySQL(7231) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5346) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4582) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2434) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1981) 功能(1967) HtmlCss(1952) Web開發(1951) C++(1928) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1879) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Java實作AWS S3 V4 Authorization自定義驗證

    # 前言 最近在開發檔案存盤服務,需要符合s3的協議標準,可以直接接入aws-sdk,本文針對sdk發出請求的鑒權資訊進行重新組合再簽名驗證有效性,sdk版本如下 ```xml software.amazon.awssdk s3 2.20.45 ``` # 演算法決議 首先對V4版本簽名演算法的資料結構 ......

    uj5u.com 2023-06-05 07:51:17 more
  • Rust Web 全堆疊開發之撰寫 WebAssembly 應用

    # Rust Web 全堆疊開發之撰寫 WebAssembly 應用 MDN Web Docs: 官網: ## 專案結構 和 功能 **Web App 教師注冊 WebService WebAssembly App 課程管理** ## 什么是 WebAssembly - WebAssembly 是一種 ......

    uj5u.com 2023-06-05 07:51:08 more
  • 包含參考型別欄位的自定義結構體,能作為map的key嗎

    # 1. 引言 在 Go 語言中,`map`是一種內置的資料型別,它提供了一種高效的方式來存盤和檢索資料。`map`是一種無序的鍵值對集合,其中每個鍵與一個值相關聯。使用 map 資料結構可以快速地根據鍵找到對應的值,而無需遍歷整個集合。 在 Go 語言中,`map` 是一種內置的資料型別,可以通過 ......

    uj5u.com 2023-06-05 07:51:00 more
  • p3 FileInputStream 和 FileOutputStream

    # FileInputStream 和 FileOutputStream ![](https://img2023.cnblogs.com/blog/3008601/202306/3008601-20230604102221520-1382311786.png) - InputStream:位元組輸入流 ......

    uj5u.com 2023-06-05 07:50:48 more
  • p2 IO流原理及流的分類

    # IO流原理及流的分類 ### 一、Java IO流原理 1. I/O是Input/Output的縮寫,I/O技術是非常實用的技術,用于處理資料傳輸。如讀/寫檔案,網路通訊等。 2. Java程式中,對于資料的輸入/輸出操作以”流(stream)“的方式進行。 3. java.io包下提供了各種” ......

    uj5u.com 2023-06-05 07:45:25 more
  • FHQ-Treap

    [模板傳送](https://www.luogu.com.cn/problem/P3369) FHQ-Treap顧名思義就是范浩強大佬設計的一種二叉平衡樹 下面我們來講一下它的原理和代碼 # 結構體 對于一個節點,我們需要記錄的是 * 對應的值 * 子樹節點數 * 左右孩子編號 * 對應的隨機值 ` ......

    uj5u.com 2023-06-05 07:40:15 more
  • p1 檔案的基本使用

    # 檔案的基本使用 ### 一、檔案 - **什么是檔案** 檔案是保存資料的地方,比如word檔案,txt檔案,excel檔案……都是檔案。即可以保存一張圖片,也可以保持視頻,聲音…… - **檔案流** 檔案在程式中是以流的形式來操作的 ![檔案流](https://img2023.cnblog ......

    uj5u.com 2023-06-05 07:40:06 more
  • Groovy 基于Groovy實作DES加解密

    groovy 3.0.7 ### DES加密簡介 加密分為對稱加密和非對稱加密。非對稱加密,加解密使用不同的密鑰,如RSA;對稱加密,加解密使用相同的密鑰,如DES(Data Encryption Standard,即資料加密標準)。相對而言,非對稱加密安全性更高,但是計算程序復雜耗時,一般只應用于 ......

    uj5u.com 2023-06-05 07:39:54 more
  • 【python基礎】復雜資料型別-串列型別(串列切片)

    # 1.串列切片 前面學習的是如何處理串列的所有資料元素。python還可以處理串列的部分元素,python稱之為切片。 ## 1.1創建切片 創建切片,可指定要使用的第一個資料元素的索引和最后一個資料元素的索引。與range函式一樣,python在到達指定的第二個索引前面的資料元素后停止。比如要輸 ......

    uj5u.com 2023-06-05 07:34:02 more
  • 基于ESP32的TCP/IP傳輸實作

    #TCP/IP協議原理 TCP/IP協議是Internet互聯網最基本的協議,TCP/IP協議的應用層的主要協議有HTTP、Telnet、FTP、SMTP等,是用來讀取來自傳輸層的資料或者將資料傳輸寫入傳輸層;傳輸層的主要協議有UDP、TCP,實作端對端的資料傳輸;網路層的主要協議有ICMP、IP、 ......

    uj5u.com 2023-06-04 07:46:19 more