首页 > 其他分享 >AK、SK实现(双方API交互:签名及验证)

AK、SK实现(双方API交互:签名及验证)

时间:2023-06-11 22:44:19浏览次数:43  
标签:resultString String AK ts param SK API log 请求

参考:https://blog.csdn.net/yqwang75457/article/details/117815474

1、原理

AK/SK:
AK:Access Key Id,用于标示用户。
SK:Secret Access Key,是用户用于加密认证字符串和用来验证认证字符串的密钥,其中SK必须保密。
通过使用Access Key Id / Secret Access Key加密的方法来验证某个请求的发送者身份。

基本思路:
1.客户端需要在认证服务器中预先设置 access key(AK 或叫 app ID) 和 secure key(SK)。
2.在调用 API 时,客户端需要对参数和 access key 等信息结合 secure key 进行签名生成一个额外的 sign字符串。
3.服务器接收到用户的请求后,系统将使用AK对应的相同的SK和同样的认证机制生成认证字符串,并与用户请求中包含的认证字符串进行比对。如果认证字符串相同,系统认为用户拥有指定的操作权限,并执行相关操作;如果认证字符串不同,系统将忽略该操作并返回错误码。

2、实现

2.1 服务端

注解拦截器,拦截请求

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface IdentifySk {
    String value() default "";
}

切面

@Component
@Aspect
public class IdentifyRequest {
    @Pointcut("@annotation(com.chinatower.platform.client.aop.IdentifySk)")
    public void pointCut() {
    }

    @Before(value = "pointCut()")
    public void before(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //获取请求头信息
        String ts = request.getHeader("ts");
        Long timeStamp = StrUtil.isNotBlank(ts) ? Long.valueOf(ts) : null;
        String msgId = request.getHeader("msgId");
        String appId = request.getHeader("appId");
        String sign = request.getHeader("sign");
        boolean res = SignUtil.checkHeaderInfo(timeStamp, msgId, appId, sign);
        Validate.isTrue(res,"请求失败,请检查请求头");
    }
}

生成sign加密工具类

@Slf4j
public class SHACoderUtil {

    public static  String sha256(String str){
        String encodeStr = "";
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
            messageDigest.update(str.getBytes("UTF-8"));
            encodeStr = byte2Hex(messageDigest.digest());
        } catch (Exception e) {
            log.error("sha256加码失败",e);
        }
        return encodeStr;
    }

    /**
     * 将byte转为16进制
     * @param bytes
     * @return
     */
    private static String byte2Hex(byte[] bytes){
        StringBuffer stringBuffer = new StringBuffer();
        String temp = null;
        for (int i=0;i<bytes.length;i++){
            temp = Integer.toHexString(bytes[i] & 0xFF);
            if (temp.length()==1){
                //1得到一位的进行补0操作
                stringBuffer.append("0");
            }
            stringBuffer.append(temp);
        }
        return stringBuffer.toString();
    }
}

校验工具类

public class SignUtil {

    /**
     * 按签名算法获取sign
     *
     * @param appId
     * @param appSecret
     * @param ts        时间戳
     * @param msgId     请求唯一标识
     * @return
     */
    public static String getSign(String appId, String appSecret, String ts, String msgId) {
        String str = IStringUtils.spliceStr("ts=", String.valueOf(ts), "&msgId=", msgId, "&appId=", appId, "&appSecret=", appSecret);
        // 对待加密字符串进行加密,得到sign值
        return SHACoderUtil.sha256(str);
    }


    /**
     * 鉴别 sign ,被调用方
     */
    public static boolean checkHeaderInfo(Long ts, String msgId, String appId, String sign) {
        if (ts == null || StrUtil.isBlank(msgId) || StrUtil.isBlank(appId) || StrUtil.isBlank(sign)) {
            return false;
        }
		//可以加一个利用redis防止重复请求
        //超过20分钟,表示请求无效
        if (System.currentTimeMillis() - ts > 20 * 60 * 1000) {
            return false;
        }
        //根据参数加密,验证和传来的sign是否一致
        String reSign = getSign(appId, CommonConstant.SK, String.valueOf(ts), msgId);
        if (sign.equals(reSign)) {
            return true;
        }
        return false;
    }

}

使用注解

    @IdentifySk()
    @PostMapping("/testSk")
    public void testSk() {
        System.out.println("ceshi");
    }

2.2 客户端

加密工具类,同上SHACoderUtil
生成请求头和sign工具类

public class SignUtil {
    /**
     * 构建请求头 调用方
     */
    public static Map<String, String> requestHeader(String appId, String appSecret) {
        String ts = String.valueOf(System.currentTimeMillis());
        String msgId = UUID.randomUUID().toString();
        Map<String, String> header = new HashMap<>(16);
        // 进行接口调用时的时间戳,即当前时间戳(毫秒),服务端会校验时间戳,例如时间差超过20分钟则认为请求无效,防止重复请求的攻击
        header.put("ts", ts);
        //每个请求提供一个唯一的标识符,服务器能够防止请求被多次使用
        header.put("msgId", msgId);
        header.put("appId", appId);
        String sign = getSign(appId, appSecret, ts, msgId);
        header.put("sign", sign);
        return header;
    }
    /**
     * 按签名算法获取sign(客户端和服务器端算法一致,都需要用)
     *
     * @param appId
     * @param appSecret
     * @param ts        时间戳
     * @param msgId     请求唯一标识
     * @return
     */
    public static String getSign(String appId, String appSecret, String ts, String msgId) {
        String str = IStringUtils.spliceStr("ts=", ts, "&msgId=", msgId, "&appId=", appId, "&appSecret=", appSecret);
        // 对待加密字符串进行加密,得到sign值
        return SHACoderUtil.sha256(str);
    }
}

远程请求http工具类

@Slf4j
public class HttpClientUtil {

    private static final String ENCODING_TYPE = "UTF-8";


    public static String doGet(String url, Map<String, String> param) {
        log.info("doGet方法,url={}, param={}", url, param);
        CloseableHttpClient httpclient = HttpClients.createDefault();

        String resultString = "";
        CloseableHttpResponse response = null;
        try {
            // 创建uri
            URIBuilder builder = new URIBuilder(url);
            if (param != null) {
                for (String key : param.keySet()) {
                    builder.addParameter(key, param.get(key));
                }
            }
            URI uri = builder.build();

            HttpGet httpGet = new HttpGet(uri);

            response = httpclient.execute(httpGet);

            // 判断返回状态是否为200
            if (response.getStatusLine().getStatusCode() == 200) {
                resultString = EntityUtils.toString(response.getEntity(), ENCODING_TYPE);
            }
            log.info("doGet方法返回状态={}, 结果={}", response.getStatusLine().getStatusCode(), resultString);
        } catch (Exception e) {
            log.error("发送get请求失败,原因={}", e.getLocalizedMessage());
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
                httpclient.close();
            } catch (IOException e) {
                log.error("关闭流失败,原因={}", e.getMessage());
                e.printStackTrace();
            }
        }
        return resultString;
    }



    public static String doPostJson(String url, String json,Integer timeOut) {
        log.info("Http 的请求地址是{}, 请求参数是 {}",url,json);
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            HttpPost httpPost = new HttpPost(url);
            // 创建请求内容
            StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
            httpPost.setEntity(entity);
            //构建超时等配置信息
            RequestConfig config = RequestConfig.custom().setConnectTimeout(timeOut) //连接超时时间
                    .setConnectionRequestTimeout(timeOut) //从连接池中取的连接的最长时间
                    .setSocketTimeout(timeOut) //数据传输的超时时间
                    .setStaleConnectionCheckEnabled(true) //提交请求前测试连接是否可用
                    .build();
            httpPost.setConfig(config);
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), ENCODING_TYPE);
        } catch (ConnectTimeoutException e){
            // 链接拒绝  ConnectTimeoutException
            log.error("发送post json 请求失败,原因={}", e);
            resultString = httpTimeOutOrConnect("连接超时", CommonConstant.HTTP_OUT_TIME);
        } catch (HttpHostConnectException e){
            // 链接拒绝  ConnectTimeoutException
            log.error("发送post json 请求失败,原因={}", e);
            resultString = httpTimeOutOrConnect("连接拒绝", CommonConstant.HTTP_OUT_TIME);
        } catch (SocketTimeoutException e){
            // 链接超时
            log.error("发送post json 请求失败,原因={}", e);
            resultString = httpTimeOutOrConnect("请求超时",CommonConstant.HTTP_OUT_TIME);
        } catch (Exception e) {
            // 其他异常
            log.error("发送post json 请求失败,原因={}", e);
            resultString = httpTimeOutOrConnect("请求失败",CommonConstant.HTTP_OUT_TIME_ERROR);
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                log.error("关闭流失败, 原因={}", e.getMessage());
                e.printStackTrace();
            }
        }
        log.info("Http 请求结果是{}",resultString);
        return resultString;
    }

    /**
     * 带请求头的的post请求
     * @param url
     * @param headMap
     * @param json
     * @param timeOut
     * @return
     */
    public static String doPostJson(String url, Map<String, String> headMap,String json,Integer timeOut) {
        log.info("Http 的请求地址是{},请求头:{}, 请求参数是 {}",url, JSON.toJSONString(headMap),json);
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            HttpPost httpPost = new HttpPost(url);
            //请求头
            if (headMap != null && !headMap.isEmpty()){
                for (String key:headMap.keySet()){
                    httpPost.addHeader(key,headMap.get(key));
                }
            }
            // 创建请求内容
            if (StrUtil.isNotBlank(json)){
                StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
                httpPost.setEntity(entity);
            }
            //构建超时等配置信息
            RequestConfig config = RequestConfig.custom().setConnectTimeout(timeOut) //连接超时时间
                    .setConnectionRequestTimeout(timeOut) //从连接池中取的连接的最长时间
                    .setSocketTimeout(timeOut) //数据传输的超时时间
                    .setStaleConnectionCheckEnabled(true) //提交请求前测试连接是否可用
                    .build();
            httpPost.setConfig(config);
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), ENCODING_TYPE);
        } catch (ConnectTimeoutException e){
            // 链接拒绝  ConnectTimeoutException
            log.error("发送post json 请求失败,原因={}", e);
            resultString = httpTimeOutOrConnect("连接超时", CommonConstant.HTTP_OUT_TIME);
        } catch (HttpHostConnectException e){
            // 链接拒绝  ConnectTimeoutException
            log.error("发送post json 请求失败,原因={}", e);
            resultString = httpTimeOutOrConnect("连接拒绝",CommonConstant.HTTP_OUT_TIME);
        } catch (SocketTimeoutException e){
            // 链接超时
            log.error("发送post json 请求失败,原因={}", e);
            resultString = httpTimeOutOrConnect("请求超时",CommonConstant.HTTP_OUT_TIME);
        } catch (Exception e) {
            // 其他异常
            log.error("发送post json 请求失败,原因={}", e);
            resultString = httpTimeOutOrConnect("请求失败",CommonConstant.HTTP_OUT_TIME_ERROR);
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
            } catch (IOException e) {
                log.error("关闭流失败, 原因={}", e.getMessage());
                e.printStackTrace();
            }
        }
        log.info("Http 请求结果是{}",resultString);
        return resultString;
    }

    /**
     *
     * @return
     */
    private static String httpTimeOutOrConnect(String returnMsg, String returnCode){
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("returnMsg", returnMsg);
        jsonObject.put("returnCode", returnCode);
        jsonObject.put("errorMsg", returnMsg);
        jsonObject.put("errorCode", returnCode);
        return jsonObject.toJSONString();
    }

}

测试请求服务端:

main(){
  Map<String, String> headMap = SignUtil.requestHeader(commonProperties.getAk(), commonProperties.getSk());
  HttpClientUtil.doPostJson(url, headMap, "ceshi", 60000);
}

标签:resultString,String,AK,ts,param,SK,API,log,请求
From: https://www.cnblogs.com/cgy1995/p/17473791.html

相关文章

  • VCS+DVE+Verdi+Makefile使用
    业界有三大仿真工具,Synopsis家的VCS、Cadence家的IUS-irun(现在是Xcelium-xrun)和Mentor的Modelsim。VCS的全称是VerilogCompileSimulator,是Synopsis公司的电路仿真工具,可以进行电路的时序模拟。VCS属于编译型verilog仿真器,内部的仿真工具是DVE。VCS先将verilog/systemverilog文......
  • CMake学习
    参考前言-《CMake菜谱(CMakeCookbook中文版)》-书栈网·BookStackTheArchitectureofOpenSourceApplications(Volume1)CMake(aosabook.org)CMakeReferenceDocumentation—CMake3.26.4DocumentationCMake从入门到精通-凌逆战-博客园(cnblogs.com)Ubun......
  • windows 安装docker desktop 报错
    安装docker时报错:JSON字符串无效。(异常来自HRESULT:0x83750007)在Windows.Data.Json.JsonValue.Parse(Stringinput)在CommunityInstaller.InstallWorkflow.SetupBackendModeAndFeatures-----------------------------------------------------------------------......
  • 2023 华北分区赛 normal_snake
    国赛终于解出Java题了,顺利拿下一血,思路之前也学过。继续加油normal_snake题目解读@RequestMapping({"/read"})publicStringhello(@RequestParam(name="data",required=false)Stringdata,Modelmodel){try{if(data.contains("!!"))......
  • Python Django Restful API simple JWT
    在这种情况下,您可以创建一个自定义权限类,并检查用户所属的任何组是否具有相应的权限。例如,您可以在Django后台为每个组定义一个具有读取权限(`view`权限)的权限对象。然后,在自定义权限类中检查用户组是否具有此权限。首先,在`models.py`文件中创建一个新的权限。例如,创建一个名......
  • 世界上最励志、最传奇的创业家Elon Musk的一天
    ElonMusk现在是世界上最励志、最传奇的创业家。他正经营着两家改变世界的公司,北美知名电动汽车生产商Tesla和私人航天探索公司SpaceX。同时他也是SolarCity的主席,为家庭用户提供太阳能的服务。他还是PayPal的早期投资人。Musk一直把他的时间掰给两家公司,周一和周四他会呆在Spac......
  • ubuntu 搭建 cmake + vscode 的 c/c++ 开发环境
    todo列表clang-formatc++整合软件安装略基本的环境搭建最基本的vscode插件只需要安装如下两个插件即可c/c++扩展是为了最基本的代码提示和调试支持cmakelanguagesupport是为了提示CMakeLists.txt脚本有可能安装了cmakelanguagesupport还是没有代码......
  • Makefile基础教程(伪目标)
    (文章目录)前言本篇文章将讲解Makefile中的伪目标,Makefile的目标在前面的文章中我们已经讲解了那么这篇文章我们就来讲讲伪目标。一、伪目标概念Makefile伪目标是一类特殊的目标,它们的目的是提供给make工具一些命令,而不是用来构建文件的。因为伪目标通常不会对应实际的文件......
  • Makefile教程(Makefile的结构)
    (文章目录)前言一、Makefile的结构Makefile通常由一系列规则组成,每条规则定义了如何从源文件生成目标文件。每个规则又由目标、依赖和命令三部分组成。下面是Makefile规则的基本结构:target:dependenciescommand1command2...其中,target是要生成的目标文......
  • Makefile教程(入门介绍)
    (文章目录)前言本篇文章将带大家学习Makefile,Makefile在文件的编译中起到重要作用,在Linux中我们也是经常使用到Makefile,下面我将会带大家学习什么是Makefile。一、Makefile介绍Makefile是用于自动化构建程序的一种工具,它通常被用于编译,连接以及打包程序等任务。Makefile利用了......