1. 程式人生 > >叢集下基於Token的API介面認證解決方案

叢集下基於Token的API介面認證解決方案

1、背景:

叢集下,多臺伺服器併發接受請求並執行三方介面的認證和請求操作,容易出現:本地token失效,叢集下的多臺伺服器,同時去三方進行認證,先後返回多個token,然而,最後一個返回的token才是最新的且是有效的。

2、方案:

叢集模式下使用

1)使用redis快取認證token,設定超時時間;

2)請求介面前,先重本地redis獲取token;

3)介面請求;

4)請求提示認證失敗,則走重新認證機制;

5)無論認證資訊來自reids,還是來自三方,則都走三方服務進行重新認證;

6)介面請求。

理論上,將可能的三方認證次數減少最多一次,也將介面請求次數降到最多兩次。

一下程式碼示例:

 /**
     * 請求360打款
     * 1.資格認證
     * 2.請求打款
     * 3.儲存日誌
     *
     * @param vo
     * @return
     */
    @Override
    public RPCResult<Boolean> exchangeMoney(ExchangeMoneyVO vo) {
        LOGGER.info("360打款,呼叫服務,引數:{}", gson.toJson(vo));
        if (StringUtils.isEmpty(vo.getCash())
                || StringUtils.isEmpty(vo.getPayId())
                || StringUtils.isEmpty(vo.getAccountName())
                || StringUtils.isEmpty(vo.getBankName())
                || StringUtils.isEmpty(vo.getBankBranch())
                || StringUtils.isEmpty(vo.getBankCardNum())) {
            return RPCResult.error("引數不全");

        }

        synchronized (vo.getPayId().intern()) {
            SanExchangeMoney sanExchangeMoney = sanExchangeMoneyService.findSuccess(vo.getPayId());
            if (null != sanExchangeMoney) {
                return RPCResult.error("360已打款成功");
            }

            sanExchangeMoney = sanExchangeMoneyService.findUnBack(vo.getPayId());
            if (null != sanExchangeMoney) {
                return RPCResult.error("360打款結果未回寫,不可重複申請");
            }

            /*1.資格認證*/
            AuthenticationRes authRes = creditExchangeMoneyService.getAuth();

            /*AuthenticationRes authRes = new AuthenticationRes("appId123", "token4556", "1");*/

            if ("0".equals(authRes.getStatus())) {
                LOGGER.error("360首次認證失敗");
                return RPCResult.error("360首次認證失敗!");
            }

            String batchNumber = sanExchangeMoneyService.getSanBatchNo();
            vo.setBatchNumber(batchNumber);

            /*2.請求打款*/
            ExchangeMoneyReq req = new ExchangeMoneyReq();
            BeanUtils.copyProperties(vo, req);
            req.setAppId(authRes.getAppId());
            req.setToken(authRes.getToken());

            /**
             * 執行請求打款
             */
            ExchangeMoneyRes res = creditExchangeMoneyService.exchangeMoney(req);

           /* ExchangeMoneyRes res = new ExchangeMoneyRes(req.getAppId(), req.getPayId(),
                    req.getBatchNumber(), "1", "申請成功");*/

            //請求打款,提示認證失敗,重新認證一次
            if ("-1".equals(res.getStatus())) {
                authRes = creditExchangeMoneyService.getAuthFrom360();

                if ("0".equals(authRes.getStatus())) {
                    LOGGER.error("360二次認證失敗");
                    return RPCResult.error("360二次認證失敗!");
                }

                req.setAppId(authRes.getAppId());
                req.setToken(authRes.getToken());

                /**
                 * 二次執行請求打款
                 * 1、redis快取token資訊失效
                 * 2、叢集環境下多例項發生同時從360獲取token
                 * 3、首次獲取token,360技術問題導致失效
                 */
                res = creditExchangeMoneyService.exchangeMoney(req);
            }

            /*3.儲存日誌*/
            SanExchangeMoney sExMoneyLog = new SanExchangeMoney();
            BeanUtils.copyProperties(vo, sExMoneyLog);
            if ("0".equals(res.getStatus())) {
                sExMoneyLog.setStatus(-1);
                sExMoneyLog.setBackTime(sExMoneyLog.getExchangeTime());
                sExMoneyLog.setReason(StringUtils.isEmpty(res.getMes()) ? "360打款請求失敗" : res.getMes());
            } else if ("-1".equals(res.getStatus())) {
                sExMoneyLog.setStatus(-1);
                sExMoneyLog.setBackTime(sExMoneyLog.getExchangeTime());
                sExMoneyLog.setReason("360認證失敗,申請打款失敗");
            }

            sanExchangeMoneyService.save(sExMoneyLog);

            if ("-1".equals(res.getStatus())) {
                LOGGER.error("360認證失敗,申請打款失敗");
                return RPCResult.error("360認證失敗,申請打款失敗");
            }

            if ("0".equals(res.getStatus())) {
                LOGGER.error("向360申請打款失敗");
                return RPCResult.error("向360申請打款失敗");
            }


            return RPCResult.success();
        }

    }

三方介面類:

import com.alibaba.fastjson.JSONObject;
import com.ddyunf.cloud.common.utils.HttpRequestUtil;
import com.ddyunf.cloud.credit.third.CreditExchangeMoneyService;
import com.ddyunf.cloud.credit.third.vo.ExchangeMoneyRes;
import com.ddyunf.cloud.enums.RedisKeyType;
import com.ddyunf.cloud.credit.third.vo.AuthenticationReq;
import com.ddyunf.cloud.credit.third.vo.AuthenticationRes;
import com.ddyunf.cloud.credit.third.vo.ExchangeMoneyReq;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;


/**
 * Created by Liuxd on 2018-08-08.
 */
@Service("creditExchangeMoneyService")
public class CreditExchangeMoneyServiceImpl implements CreditExchangeMoneyService {

    private static final Logger LOGGER = LoggerFactory.getLogger(CreditExchangeMoneyServiceImpl.class);

    private final static Gson gson = new Gson();


    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Value("${san.appId}")
    private String appId;

    @Value("${san.appSecret}")
    private String appSecret;

    @Value("${san.url}")
    private String url;

    /**
     * 獲取認證資訊
     *
     * @return
     */
    @Override
    public AuthenticationRes getAuth() {
        AuthenticationRes authRes = getAuthFromRedis();
        if ("0".equals(authRes.getStatus())) {
            authRes = getAuthFrom360();
        }

        return authRes;
    }

    @Override
    public AuthenticationRes getAuthFromRedis() {

        Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(RedisKeyType.SAN_AUTH.getValue());
        if (null != map && !map.isEmpty()
                && null != map.get("appId") && !StringUtils.isEmpty(map.get("appId").toString())
                && null != map.get("token") && !StringUtils.isEmpty(map.get("token").toString())) {

            return new AuthenticationRes(0,map.get("appId").toString(), map.get("token").toString());
        }

        return new AuthenticationRes("空");


    }

    @Override
    public AuthenticationRes getAuthFrom360() {
        AuthenticationReq req = new AuthenticationReq(appId, appSecret);

        String reqJson = JSONObject.toJSONString(req);

        LOGGER.info("360認證,請求報文:{}", reqJson);
        String resStr = null;
        try {
            resStr = HttpRequestUtil.sendPostString(url, reqJson);
        } catch (Exception e) {
            e.printStackTrace();
        }
        LOGGER.info("360認證,響應報文:{}", resStr);

        if (null == resStr) {
            LOGGER.info("360認證返回為空,請求報文:{}", reqJson);
            return new AuthenticationRes("360認證返回為空!");
        }

        AuthenticationRes res = gson.fromJson(resStr, AuthenticationRes.class);

        if ("1".equals(res.getStatus())) {
            Map<Object, Object> map = new HashMap<>();
            map.put("appId", res.getAppId());
            map.put("token", res.getToken());
            stringRedisTemplate.opsForHash().putAll(RedisKeyType.SAN_AUTH.getValue(), map);
            stringRedisTemplate.expire(RedisKeyType.SAN_AUTH.getValue(), 30, TimeUnit.MINUTES);
        }

        res.setFrom(1);

        return res;
    }

    @Override
    public ExchangeMoneyRes exchangeMoney(ExchangeMoneyReq req) {

        String reqJson = JSONObject.toJSONString(req);

        LOGGER.info("360打款,請求報文:{}", reqJson);
        String resStr = null;
        try {
            resStr = HttpRequestUtil.sendPostString(url, reqJson);
        } catch (Exception e) {
            e.printStackTrace();
        }
        LOGGER.info("360打款,響應報文:{}", resStr);

        if (null == resStr) {
            LOGGER.info("360打款返回為空,請求報文:{}", reqJson);
            return new ExchangeMoneyRes("360打款返回為空!");
        }

        ExchangeMoneyRes res = gson.fromJson(resStr, ExchangeMoneyRes.class);

        return res;
    }


}