公有云API的认证方式:Token认证和AK/SK认证

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了公有云API的认证方式:Token认证和AK/SK认证相关的知识,希望对你有一定的参考价值。

参考技术A 公有云API的认证方式

一般有以下几种认证方式:
Token认证
AK/SK认证
RSA非对称加密方式

Token认证
使用Token认证方式完成认证鉴权时,用户首先需要获取token,在调用接口时增加“X-XXX-Token”到业务接口请求消息头中。
流程

原理:任何请求,都附带token;服务端根据token判断请求是否合法。
缺点:若是报文在中途被劫持,那么token就泄露了,这时(token有效期内)黑客就能够构造任意的请求了。

2 AK/SK认证
2.1 AK/SK 原理
云主机需要通过使用Access Key Id / Secret Access Key加密的方法来验证某个请求的发送者身份。
Access Key Id(AK)用于标示用户,Secret Access Key(SK)是用户用于加密认证字符串和云厂商用来验证认证字符串的密钥,其中SK必须保密。 AK/SK原理使用对称加解密。

2.2 AK/SK使用机制
云主机接收到用户的请求后,系统将使用AK对应的相同的SK和同样的认证机制生成认证字符串,并与用户请求中包含的认证字符串进行比对。如果认证字符串相同,系统认为用户拥有指定的操作权限,并执行相关操作;如果认证字符串不同,系统将忽略该操作并返回错误码。
2.3 流程
判断用户请求中是否包含Authorization认证字符串。如果包含认证字符串,则执行下一步操作。
基于HTTP请求信息,使用相同的算法,生成Signature字符串。
使用服务器生成的Signature字符串与用户提供的字符串进行比对,如果内容不一致,则认为认证失败,拒绝该请求;如果内容一致,则表示认证成功,系统将按照用户的请求内容进行操作。

原理:

Restful API AK/SK认证

AK/SK简介

AK(Access Key ID,用于标识用户)/SK(Secret Access Key,是用户用于加密认证的字符串和验证认证字符串的密钥,SK必须保密),主要用于对用户的调用行为进行鉴权和认证,相当于专用的用户名和密码

AK/SK认证流程

客户端根据双方协商好的规则算法生成Signature认证字符串,并将生成的Signature认证字符串设置到header中。当API网关/服务端接收到请求后,判断请求中是否包含Signature认证字符串。如果包含认证字符串,则执行下一步操作。
基于HTTP请求信息,使用与客户端相同的规则算法,生成Signature字符串并于与客户端提供的Signature字符串进行比对,如果内容不一致,则认为认证失败,拒绝该请求;如果内容一致,则表示认证成功,系统将按照客户端的请求内容进行操作。

客户端:
    构建http请求(包含 access key);
    使用请求内容和 使用secret access key计算的签名(signature);
    发送请求到服务端。
服务端:
    根据发送的access key 查找数据库获得对应的secret-key;
    使用一样的算法将请求内容和 secret-key一块儿计算签名(signature),和步骤2同样;
    对比用户发送的签名和服务端计算的签名,二者相同则认证经过,不然失败。

实现基本思路

  1. 客户端需要在认证服务器中预先设置(AK 或叫 app ID) 和 SK。
  2. 获取当前时间时间戳并生成请求唯一标识(随机码)
  3. 在调用API前,客户端需要将对 时间戳、请求标识、请求参数结合SK进行签名生成一个额外的sign字符串
  4. 将时间戳、请求标识、AK以及生成的sign字符串设置到请求header中
  5. 服务端收到客户端的请求后,先判断header中设置的四类认证数据是否存在。
  6. 根据header中的时间戳与当前时间比对判断是否该请求以过期,防止抓包后的恶意请求
  7. 根据header中的请求标识判断出该请求是否唯一(每次请求将唯一标识保存,待下次请求进来后进行比对判断。可设置保存时长)
  8. 根据AK获取客户端预先在认证服务器设置好的SK
  9. 将时间戳、请求标识、请求参数结合客户端预先设置好的SK使用与客户端相同的签名生成方式生成一个临时的sign字符串并与客户端请求中包含的sign字符串比较。
  10. 5、6、7、8、9这五步全部通过继续执行下一步操作,否则认证失败返回错误码

代码实现

基于上面的实现思路,大致写下代码,代码中加的有详细注释,逻辑就不一一解释了,写的比较简单。

拦截器懒得写了哈,我这就直接通过AOP 前置通知来实现认证信息的获取以及认证

@Before("executePointcut()")
    public void before(JoinPoint joinPoint)
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        Object[] args = joinPoint.getArgs();
        JSONObject json = (JSONObject) args[args.length - 1];
        Map sortedMap = JsonToMap.sortParams(json);
        Long timeStamp = Long.parseLong(request.getHeader("TimeStamp"));
        String nonce = request.getHeader("nonce");
        String s = map.get(nonce);
        if (s!=null)
            log.error("重复的请求...");
            Asserts.fail("Repeat request");
        
        map.put(nonce,nonce);
        //开启守护线程 清除请求唯一标识
        executorService.execute(new RemoveMapRunnable(nonce));
        String sign = request.getHeader("sign");
        if (timeStamp==null||timeStamp<1||StringUtils.isNotEmpty(nonce)
        ||StringUtils.isNotEmpty(sign))
            long endTime = System.currentTimeMillis();
            if (endTime-timeStamp > l)
                log.error("请求过期失效..");
                Asserts.fail("Request expired");
            
        else
            log.error("认证参数缺失..");
            Asserts.fail("Missing authentication parameters");
        
        if(!SignUtil.checkReqInfo(timeStamp, nonce, sign, sortedMap))
            log.error("认证失败,sign=",sign);
            Asserts.fail("Authentication failed");
        
        log.info("认证成功...");
    
    
    private class RemoveMapRunnable implements Runnable
        private String nonce;
        public RemoveMapRunnable(String nonce)
            this.nonce = nonce;
        
        @Override
        public void run() 
            synchronized (this)
                try 
                    Thread.sleep(l);
                    map.remove(this.nonce);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
            
        
    
/**
     * @Author lijl
     * @MethodName wrapperHeader
     * @Description 通过请求参数,包装请求header信息(含签名信息)
     * @Date 16:14 2021/11/11
     * @Version 1.0
     * @param reqParam
     * @return: sign=02C89AD7CEC9C05831520015CD7C3413F1DE03822D2DA015A7B353B7E7F38E7D, nonce=6b10f2ee-aba6-4032-bc9f-ca82c76b30d1, TimeStamp=1636684729852
    **/
    public static Map<String, Object> wrapperHeader(Map<String, Object> reqParam) 
        Long ts = System.currentTimeMillis();
        String nonce = UUID.randomUUID().toString();
        Map<String, Object> header = new HashMap<>();
        //进行接口调用时的时间戳,即当前时间戳(毫秒),服务端会校验时间戳,例如时间差超过30秒则认为请求无效,防止重复请求的攻击
        header.put("TimeStamp", ts);
        //每个请求提供一个唯一的标识符,服务器能够防止请求被多次使用
        header.put("nonce", nonce);
        //按签名算法获取sign
        String sign = getSign(appSecret, ts, nonce, reqParam);
        header.put("sign", sign);
        return header;
    

    /**
     * @Author lijl
     * @MethodName getSign
     * @Description 按签名算法获取sign
     * @Date 16:04 2021/11/11
     * @Version 1.0
     * @param appSecret
     * @param ts
     * @param nonce
     * @param reqParam
     * @return: java.lang.String
    **/
    private static String getSign(String appSecret, Long ts, String nonce, Map<String, Object> reqParam) 
        // 计算签名规则:sign = HMACSHA256("ts=1623388123195&noce=d50e301d-ee2c-446e-8f28-013f0fee09fb&appSecret=1ZLAzEgQHfBd19vSapdL8lxzA&1=2&1=2")
        // 1.请求参数key升序
        // 2.待加密字符串
        StringBuffer s = new StringBuffer();
        s.append("&ts=").append(ts).append("&noce=").append(nonce).append("&appSecret=").append(appSecret);
        reqParam.forEach((k, v) -> s.append("&").append(k).append("=").append(v));
        // 3.对待加密字符串进行加密(对字符串HMACSHA256处理,得到sign值)
        try 
            return HMACSHA256(s.toString());
         catch (Exception e) 
            e.printStackTrace();
        
        return null;
    

    /**
     * @Author lijl
     * @MethodName checkReqInfo
     * @Description 验证请求是否有效
     * @Date 10:36 2021/11/12
     * @Version 1.0
     * @param ts
     * @param nonce
     * @param sign
     * @param reqParam
     * @return: 是否有效(方便测试我用Boolean,可根据业务需要,返回对应错误信息,不一定用Boolean)
    **/
    public static Boolean checkReqInfo(Long ts, String nonce, String sign,Map<String, Object> reqParam) 
        String srvSign = getSign(appSecret, ts, nonce, reqParam);
        // 目前能想到的安全验证就这些,或许大家还能想到其他验证,让接口更加安全
        return sign.equalsIgnoreCase(srvSign);
    

    /**
     * @Author lijl
     * @MethodName HMACSHA256
     * @Description HMAC-SHA256算法
     * @Date 10:32 2021/11/12
     * @Version 1.0
     * @param data
     * @return: java.lang.String
    **/
    public static String HMACSHA256(String data) throws Exception 
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(appSecret.getBytes("UTF-8"), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte item : array) 
            sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
        
        return sb.toString().toUpperCase();
    

    public static void main(String[] args) 
        Map<String, Object> reqParam = new HashMap<String, Object>();
        reqParam.put("1", "2");
        reqParam.put("2", "1");
        //请求头(行sign值等信息)
        Map<String, Object> reqHeader = wrapperHeader(reqParam);
        System.out.println(reqHeader);
        // ==================客户端发起请求,参数param,并把header带入请求中

        // ============================服务器端,收到请求
        // 1.验证请求信息
        // 2处理业务逻辑
        // 3.返回数据到客户端
        long ts = (long) reqHeader.get("TimeStamp");
        String nonce = (String) reqHeader.get("nonce");
        String sign = (String) reqHeader.get("sign");
        Boolean valid = checkReqInfo(ts,nonce,sign,reqParam);
        if (valid)
            System.out.println("有效请求,继续处理...");
        else 
            System.out.println("无效");
        
    

以上是关于公有云API的认证方式:Token认证和AK/SK认证的主要内容,如果未能解决你的问题,请参考以下文章

Restful API AK/SK认证

Restful API AK/SK认证

Restful API AK/SK认证

Restful API AK/SK认证

LaravelLaravel的auth认证中间件的Api-token详细配置说明

API和API网关的认识