叢集下基於Token的API介面認證解決方案
阿新 • • 發佈:2019-02-06
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; } }