集群下基于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 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 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 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;
    }


}

 

你可能感兴趣的:(解决方案)