原创

V3手动鉴权失败之Java篇

导语

该系列其他篇章:

V3手动鉴权失败之Nodejs篇

V3手动鉴权失败之Go篇

V3手动鉴权失败之Python篇

V3手动鉴权失败之PHP篇

V3手动鉴权失败之C#篇

腾讯云 API 全新升级 3.0 ,该版本进行了性能优化且全地域部署、支持就近和按地域接入、访问时延下降显著,接口描述更加详细、错误码描述更加全面、SDK增加接口级注释,让您更加方便快捷的使用腾讯云产品。人脸识别文字识别语音识别等众多产品均已接入云API 3.0。

腾讯云API为了更好的让用户接入,已经封装好了多种语言的SDK,只需用户传入SecrectId、SectectKey以及接口入参,即可完成接口鉴权和请求发送,具体包括Python SDKJava SDKPHP SDKGo SDKNodeJS SDK.NET SDK

案例背景

在某些情况,用户需要实现手动接口鉴权,虽然官网文档已有详细的接口鉴权流程,但是由于:

1.V3手动鉴权步骤较为复杂;

2.官网某些demo代码无法直接下载运行,仍需简单调整;

3.官网文档的demo代码覆盖面有限,没有包括全量上述六类后端语言;

基于此,很多用户只能自己尝试手动鉴权,但都返回“鉴权失败”,从而无法调通接口。

原因分析

从宏观上看,“鉴权失败”要关注两个阶段:

1. 整体的接口鉴权是否正确;

2. 模拟的鉴权请求的发送是否正确;

从历史问题回顾,有客户曾经出现接口鉴权时而成功,时而失败的情况,排查了整体的鉴权过程,完全正确,但是也的确复现了客户的问题。后来发现,用户在鉴权完成后,发送具体的请求时,传入的时间戳timestamp没有实时更新导致了报错。

解决方案

为了帮助客户更简单、更快捷地完成接口手动鉴权,并成功发送鉴权请求,将通过一系列文章专门讲解各个后端语言的手动鉴权&发送请求的可执行demo代码,助力客户快速接入。

本期将以调用人脸识别的DetectFace接口以及文字识别的BusinessCardOCR名片识别接口为例,详叙Java语言demo。

前期准备

Java语言环境:直接在Java官网根据操作系统类型下载并安装指定SDK安装包即可。

SecrectId和SecretKey:接口鉴权的密钥。可以把SecretId理解成“账号”,把SecretKey理解成“密码”。在自己的腾讯云官网控制台获取:访问管理 -> 访问密钥 -> API密钥管理。

手动鉴权相关文档:请求结构公共参数V3接口鉴权

Java代码IDE:笔者使用IDEA,下载并安装,方便使用maven进行包管理。

具体代码

新建一个Maven项目,方便之后的第三方jar包管理:

新建Maven项目

下面介绍两种方法来实现V3接口鉴权,一种是模拟curl请求,一种是直接发送HTTP请求

method01

在pom.xml文件中添加javax.xml.bind的第三方依赖:

添加javax.xml.bind第三方依赖

新建名为method01.java的代码文件,具体代码如下(包括了http请求发送和接收响应):

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class method01 {
    private final static Charset UTF8 = StandardCharsets.UTF_8;
    private final static String SECRET_ID = "xxx";//传入自己的secretId
    private final static String SECRET_KEY = "xxx";//传入自己的secretKey

    public static byte[] hmac256(byte[] key, String msg) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
        mac.init(secretKeySpec);
        return mac.doFinal(msg.getBytes(UTF8));
    }

    public static String sha256Hex(String s) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] d = md.digest(s.getBytes(UTF8));
        return DatatypeConverter.printHexBinary(d).toLowerCase();
    }

    public static void main(String[] args) throws Exception {
        String service = "iai";
        String host = "iai.tencentcloudapi.com";
        String region = "ap-guangzhou";
        String action = "DetectFace";
        String version = "2018-03-01";
        String algorithm = "TC3-HMAC-SHA256";
//        String timestamp = "1566183698";
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        // 注意时区,否则容易出错
        sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
        String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));

        // ************* 步骤 1:拼接规范请求串 *************
        String httpRequestMethod = "POST";
        String canonicalUri = "/";
        String canonicalQueryString = "";
        String canonicalHeaders = "content-type:application/json\n" + "host:" + host + "\n";
        String signedHeaders = "content-type;host";
//        String payload = "{\"Limit\": 1, \"Filters\": [{\"Values\": [\"\\u672a\\u547d\\u540d\"], \"Name\": \"instance-name\"}]}";
        String payload = "{\"Url\":\"https://file.digitaling.com/eImg/uimages/20171009/1507508998226085.jpg\"}";
//        payload = "{\"Url\":\"https://file.digitaling.com/eImg/uimages/20171009/1507508998226085.jpg\"}";
        System.out.println(payload);
        String hashedRequestPayload = sha256Hex(payload);
        String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n"
                + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload;
//        System.out.println("CanonicalRequest is: \n");
        System.out.println(canonicalRequest);

        // ************* 步骤 2:拼接待签名字符串 *************
        String credentialScope = date + "/" + service + "/" + "tc3_request";
        String hashedCanonicalRequest = sha256Hex(canonicalRequest);
        String stringToSign = algorithm + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;
        System.out.println("Timestamp is:"+timestamp+"\n");
        System.out.println("Date is: "+date+"\n");
//        System.out.println("StingToSign is: \n");
        System.out.println(stringToSign);

        // ************* 步骤 3:计算签名 *************
        byte[] secretDate = hmac256(("TC3" + SECRET_KEY).getBytes(UTF8), date);
        byte[] secretService = hmac256(secretDate, service);
        byte[] secretSigning = hmac256(secretService, "tc3_request");
        String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase();
//        System.out.println("Signature is:\n");
        System.out.println(signature);

        // ************* 步骤 4:拼接 Authorization *************
        String authorization = algorithm + " " + "Credential=" + SECRET_ID + "/" + credentialScope + ", "
                + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
//        System.out.println("Authorization is: \n");
        System.out.println(authorization);

        payload = "{\\\"Url\\\":\\\"https://file.digitaling.com/eImg/uimages/20171009/1507508998226085.jpg\\\"}";
        StringBuilder sb = new StringBuilder();
        sb.append("curl -X POST https://").append(host)
                .append(" -H \"Authorization: ").append(authorization).append("\"")
                .append(" -H \"Content-Type: application/json\"")
                .append(" -H \"Host: ").append(host).append("\"")
                .append(" -H \"X-TC-Action: ").append(action).append("\"")
                .append(" -H \"X-TC-Timestamp: ").append(timestamp).append("\"")
                .append(" -H \"X-TC-Version: ").append(version).append("\"")
                .append(" -H \"X-TC-Region: ").append(region).append("\"")
                .append(" -d \"").append(payload).append("\"");
//        System.out.println("sb is: \n");
        System.out.println(sb.toString());

        Process process = Runtime.getRuntime().exec(sb.toString());
        BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String line = null;
        StringBuilder stringBuilder = new StringBuilder();
        while ((line = br.readLine()) != null) {
            stringBuilder.append(line + "\n");
        }
        System.out.println(stringBuilder.toString());
    }
}

method02

在pom.xml文件中添加Gson的第三方依赖:

引入Gson第三方依赖

新建名为method02.java的代码文件,具体代码如下(包括了http请求发送和接收响应):

import com.google.gson.Gson;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

public class method02 {
    private static String SecretId  = "xxx";//传入自己的secretId
    private static String SecretKey = "xxx";//传入自己的secretKey

    private static String Url       = "https://ocr.tencentcloudapi.com";
    //规范请求串
    private static String  HTTPRequestMethod     = "POST";
    private static String  CanonicalURI          = "/";
    private static String  CanonicalQueryString  = "";
    private static String  CanonicalHeaders      = "content-type:application/json; charset=utf-8\nhost:ocr.tencentcloudapi.com\n";
    private static String  SignedHeaders         = "content-type;host";//参与签名的头部信息

    //签名字符串
    private static String  Algorithm             = "TC3-HMAC-SHA256";
    private static String  Service               = "ocr";
    private static String  Stop                  = "tc3_request";

    //版本
    public static String  Version               = "2018-11-19";
    public static String  Region                = "ap-beijing";


    /***
     * 示例名片请求方法
     * @param args
     */
    public static void main(String [] args) {
        Map<String, Object> params = new HashMap<>();
        params.put("ImageUrl", "https://ocr-demo-1254418846.cos.ap-guangzhou.myqcloud.com/card/BusinessCardOCR/BusinessCardOCR1.jpg");
        Gson gson = new Gson();
        String param = gson.toJson(params);
        //发送请求 本地封装的https 请求
        String response = getAuthTC3("BusinessCardOCR", param, Version);
        //打印请求数据
        System.out.println(response);
    }

    /**
     * v3鉴权
     * @param action  方法名
     * @param paramJson json化的参数
     * @param version  版本号 2018-03-01
     * @return
     */
    public static String getAuthTC3(String action, String paramJson, String version){
        try{
            String hashedRequestPayload   =   HashEncryption(paramJson);
            String CanonicalRequest =
                    HTTPRequestMethod + '\n' +
                            CanonicalURI + '\n' +
                            CanonicalQueryString + '\n' +
                            CanonicalHeaders + '\n' +
                            SignedHeaders + '\n' +
                            hashedRequestPayload;
            //时间戳
            Date date = new Date();
            //微秒->秒
            String timestamp    =   String.valueOf(date.getTime() / 1000);

            //格林威治时间转化
            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
            formatter.setTimeZone(TimeZone.getTimeZone("GMT+0"));
            String dateString = formatter.format(date.getTime());

            //签名字符串
            String credentialScope = dateString + "/" + Service + "/" + Stop;
            String hashedCanonicalRequest =   HashEncryption(CanonicalRequest);
            String stringToSign           =   Algorithm + "\n" +
                    timestamp + "\n" +
                    credentialScope + "\n" +
                    hashedCanonicalRequest;

            //计算签名
            byte[] secretDate             =   HashHmacSha256Encryption(("TC3" + SecretKey).getBytes("UTF-8"), dateString);
            byte[] secretService          =   HashHmacSha256Encryption(secretDate, Service);
            byte[] secretSigning          =   HashHmacSha256Encryption(secretService, Stop);

            //签名字符串
            byte[] signatureHmacSHA256    =   HashHmacSha256Encryption(secretSigning, stringToSign);

            StringBuilder builder = new StringBuilder();
            for (byte b : signatureHmacSHA256) {
                String hex = Integer.toHexString(b & 0xFF);
                if (hex.length() == 1) {
                    hex = '0' + hex;
                }
                builder.append(hex);
            }
            String signature = builder.toString().toLowerCase();
            //组装签名字符串
            String authorization          =   Algorithm + ' ' +
                    "Credential=" + SecretId + '/' + credentialScope + ", " +
                    "SignedHeaders=" + SignedHeaders + ", " +
                    "Signature=" + signature;

            //创建header 头部
            Map<String, String> headers = new HashMap<>();
            headers.put("Authorization", authorization);
            headers.put("Host", "ocr.tencentcloudapi.com");
            headers.put("Content-Type", "application/json; charset=utf-8");
            headers.put("X-TC-Action", action);
            headers.put("X-TC-Version", version);
            headers.put("X-TC-Timestamp", timestamp);
            headers.put("X-TC-Region", Region);
            //request 请求
            String response = resquestPostData(Url, paramJson, headers);
            return response;
        }catch(Exception e){
            return e.getMessage();
        }
    }

    /*
     * Function  :   发送Post请求到服务器
     * Param     :   params请求体内容,encode编码格式
     */
    public static String resquestPostData(String strUrlPath, String data, Map<String, String> headers) {
        try {
            URL url = new URL(strUrlPath);
            HttpURLConnection httpURLConnection = (HttpURLConnection)url.openConnection();
            httpURLConnection.setConnectTimeout(3000);            //设置连接超时时间
            httpURLConnection.setDoInput(true);                  //打开输入流,以便从服务器获取数据
            httpURLConnection.setDoOutput(true);                 //打开输出流,以便向服务器提交数据
            httpURLConnection.setRequestMethod("POST");          //设置以Post方式提交数据
            httpURLConnection.setUseCaches(false);               //使用Post方式不能使用缓存
            //设置header
            if (headers.isEmpty()) {
                //设置请求体的类型是文本类型
                httpURLConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
            } else {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    String key = entry.getKey();
                    String value = entry.getValue();
                    httpURLConnection.setRequestProperty(key, value);
                }
            }
            //设置请求体的长度
            httpURLConnection.setRequestProperty("Content-Length", String.valueOf(data.length()));

            //获得输出流,向服务器写入数据
            if (data != null) {
                byte[] writebytes = data.getBytes();
                // 设置文件长度
                OutputStream outputStream = httpURLConnection.getOutputStream();
                outputStream.write(data.getBytes());
                outputStream.flush();
            }
            int response = httpURLConnection.getResponseCode();            //获得服务器的响应码
            if(response == HttpURLConnection.HTTP_OK) {
                InputStream inptStream = httpURLConnection.getInputStream();
                return dealResponseResult(inptStream);                     //处理服务器的响应结果
            }
        } catch (IOException e) {
            return "err: " + e.getMessage().toString();
        }
        return "-1";
    }

    /*
     * Function  :   封装请求体信息
     * Param     :   params请求体内容,encode编码格式
     */
    public static StringBuffer getRequestData(Map<String, String> params, String encode) {
        StringBuffer stringBuffer = new StringBuffer();        //存储封装好的请求体信息
        try {
            for(Map.Entry<String, String> entry : params.entrySet()) {
                stringBuffer.append(entry.getKey())
                        .append("=")
                        .append(URLEncoder.encode(entry.getValue(), encode))
                        .append("&");
            }
            stringBuffer.deleteCharAt(stringBuffer.length() - 1);    //删除最后的一个"&"
        } catch (Exception e) {
            e.printStackTrace();
        }
        return stringBuffer;
    }

    /*
     * Function  :   处理服务器的响应结果(将输入流转化成字符串)
     * Param     :   inputStream服务器的响应输入流
     */
    public static String dealResponseResult(InputStream inputStream) {
        String resultData = null;      //存储处理结果
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] data = new byte[1024];
        int len = 0;
        try {
            while((len = inputStream.read(data)) != -1) {
                byteArrayOutputStream.write(data, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        resultData = new String(byteArrayOutputStream.toByteArray());
        return resultData;
    }


    /**
     *
     */
    private static String  HashEncryption(String s)  throws Exception {
        MessageDigest sha = MessageDigest.getInstance("SHA-256");
        sha.update(s.getBytes());
        //替换java DatatypeConverter.printHexBinary(d).toLowerCase()
        StringBuilder builder = new StringBuilder();
        for (byte b : sha.digest()) {
            String hex = Integer.toHexString(b & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            builder.append(hex);
        }
        return builder.toString().toLowerCase();
    }

    private static byte[] HashHmacSha256Encryption(byte[] key, String msg) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
        mac.init(secretKeySpec);
        return mac.doFinal(msg.getBytes("UTF-8"));
    }
}

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • V3手动鉴权失败之Nodejs篇

    腾讯云 API 全新升级 3.0 ,该版本进行了性能优化且全地域部署、支持就近和按地域接入、访问时延下降显著,接口描述更加详细、错误码描述更加全面、SDK增加接...

    周朋伟
  • V3手动鉴权失败之Go篇

    腾讯云 API 全新升级 3.0 ,该版本进行了性能优化且全地域部署、支持就近和按地域接入、访问时延下降显著,接口描述更加详细、错误码描述更加全面、SDK增加接...

    周朋伟
  • V3手动鉴权失败之Python篇

    腾讯云 API 全新升级 3.0 ,该版本进行了性能优化且全地域部署、支持就近和按地域接入、访问时延下降显著,接口描述更加详细、错误码描述更加全面、SDK增加接...

    周朋伟
  • V3手动鉴权失败之C#篇

    腾讯云 API 全新升级 3.0 ,该版本进行了性能优化且全地域部署、支持就近和按地域接入、访问时延下降显著,接口描述更加详细、错误码描述更加全面、SDK增加接...

    周朋伟
  • V3手动鉴权失败之PHP篇

    腾讯云 API 全新升级 3.0 ,该版本进行了性能优化且全地域部署、支持就近和按地域接入、访问时延下降显著,接口描述更加详细、错误码描述更加全面、SDK增加接...

    周朋伟
  • 【AI接入迷你赛】腾讯云产品鉴权签名 v3

    腾讯云 API 会对每个请求进行身份验证,用户需要使用安全凭证,经过特定的步骤对请求进行签名 Signature,每个请求都需要在公共请求参数中指定该签名结果并...

    用户4299935
  • 【玩转腾讯云】【腾讯云语音合成】智能语音交互之语音合成篇

    语音合成(Text To Speech,TTS)满足将文本转化成拟人化语音的需求,打通人机交互闭环。 提供多种音色选择,支持自定义音量、语速,让发音更自然、更专...

    ruskin
  • docker-compose下的java应用启动顺序两部曲之一:问题分析

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    程序员欣宸
  • 实战:Bean的数据完整性验证方法| 从开发角度看应用架构11

    Java应用程序将数据存储在Java对象中。这些Java对象通过网络,作为参数传递给方法,并存在于Java EE应用程序的不同层中。为了保持数据完整性,数据验证...

    魏新宇

扫码关注云+社区

领取腾讯云代金券