
GraphQL API滲透測試指南
接口文檔地址: https://www.coderutil.com/apiopen?tab=hotlist&pk=2000?
其實做前幾天的文章中介紹過程序員盒子使用的多集緩存方案,沒有看多的兄弟可以先了解下這篇文章(因為這里我們就不展開講了,直接show me code):
性能優化|前端LocalStorage + Google Cache + Redis三級緩存性能優化
其實服務端接口這里還是用的Google cache+ redis實現兩級緩存,這里并沒有入庫,完全依賴redis:
/**
* 微博熱搜
* @return
*/
@GetMapping("/api/resou/v1/weibo")
public APIResponseBean weiboHotSearch(@RequestParam(value = "size", defaultValue = "10") Integer size) {
List<WeiboHotSearchResultBaseVO> result = hotSearchService.getWeiboHotSearchListFromCache(size);
return APIResponseBeanUtil.success(result);
}
針對跨域請求問題,我們還提供了jsonp跨域接口
/**
* 百度熱搜
* @return
*/
@RequestMapping(value = "/api/resou/v1/weibo.jsonp", produces = "text/script;charset=UTF-8", method= RequestMethod.GET)
public String weiboHotSearchJsonp(HttpServletRequest request, String callback) {
String sizeParam = request.getParameter("size");
int size = StringUtils.isBlank(sizeParam) ? DEFAULT_SIZE : Integer.valueOf(sizeParam);
List<WeiboHotSearchResultBaseVO> result = hotSearchService.getWeiboHotSearchListFromCache(size);
return callback + "(" + JsonUtil.toJsonString(result) + ")";
}
@Autowired
private RedisService redisService;
@Autowired
private ApiUrlConfig apiUrlConfig;
private static final Cache<String, String> RESOU_LOCAL_CACHE;
static {
RESOU_LOCAL_CACHE = CacheBuilder.newBuilder()
.softValues()
.maximumSize(10L)
.expireAfterWrite(300L, TimeUnit.SECONDS)
.build();
}
/**
* 微博熱搜
* @return
*/
public List<WeiboHotSearchResultBaseVO> getWeiboHotSearchListFromCache(int size) {
String localCacheKey = LocalCacheKey.WEIBO.name();
String localCacheVal = RESOU_LOCAL_CACHE.getIfPresent(localCacheKey);
List<WeiboHotSearchResultBaseVO> list;
if (StringUtils.isNotBlank(localCacheVal)) {
list = JsonUtil.fromJson(localCacheVal, new TypeReference<List<WeiboHotSearchResultBaseVO>>() {});
} else {
String key = RedisKeyEnum.WEIBO_HOT_SEARCH_LIST_CACHE.getKey();
String val = redisService.get(key);
if (StringUtils.isNotBlank(val)) {
list = JsonUtil.fromJson(val, new TypeReference<List<WeiboHotSearchResultBaseVO>>() {});
} else {
// 上篇文章中有這個刷新方法的具體實現邏輯
list = refreshAndGetWeiboHotSearchListCache();
}
// 本地緩存如果沒有則刷新到本地緩存
RESOU_LOCAL_CACHE.put(localCacheKey, JsonUtil.toJsonString(list));
}
if (CollectionUtils.isNotEmpty(list)) {
list = list.subList(0, size > list.size() ? list.size() : size);
}
return list;
}
ok,兩集緩存實現接口響應性能到底怎么樣,我們看服務端日志穩定在10ms之內:
開放z z鑒權Aksk是做常見的做法之一,這里我們其實實現的也比較簡單,具體方案如下圖所示:
(1)用戶請求接口,header頭邀請攜帶自己的app_key、secret_key
(2)APIFilter攔截API請求,做ak、sk做驗證
(3)aksk驗證通過請求api,請求緩存獲取數據
(4)返回數據
@Slf4j
@Order(1)
@WebFilter(filterName = "apiRequestFilter", urlPatterns = {"/api/poster/*", "/api/resou/*", "/api/url/*",
"/api/upload/*", "/api/text/*", "/api/ip/*", "/api/music/*", "/api/yulu/*", "/api/openai/*", "/rmi/*"})
public class ApiRequestFilter implements Filter {
@Autowired
private ApiInvokeRecordService apiInvokeRecordService;
@Autowired
private AccessSecretKeyService accessSecretKeyService;
/***
* 開放API清單
*/
private static Map<String, String> openApiMap = new ConcurrentHashMap<>();
private static List<String> WHITE_API_LIST = new ArrayList<>();
static {
// 需要Ak、Sk鑒權的請求
openApiMap.put("/api/resou/v1/weibo", "微博熱搜");
/**
* 白名單
*/
WHITE_API_LIST.add("/api/poster/qr.temp");
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String uri = request.getRequestURI();
if (isWhiteApiUri(uri)) {
// 跳過鑒權
filterChain.doFilter(servletRequest, servletResponse);
return;
}
if (uri != null && uri.startsWith("rmi")) {
String accessKey = request.getHeader("access-key");
String secretKey = request.getHeader("secret-key");
if (StringUtils.isBlank(accessKey) || StringUtils.isBlank(secretKey)) {
APIResponseBean apiResponse = APIResponseBeanUtil.error(401, "無權限!");
backErrorMessage(response, apiResponse);
return;
}
}
if (isApiUri(uri)) {
// 獲取API請求的認證參數:accessKey secretKey
String accessKey = request.getHeader("access-key");
String secretKey = request.getHeader("secret-key");
// AK、SK 鑒權(走緩存查詢)
APIResponseBean apiResponseBean = accessSecretKeyService.checkRequestAkSk(accessKey, secretKey);
if (!apiResponseBean.getSuccess()) {
log.error("ApiRequestFilter.checkRequestAkSk error, AK:{}, SK:{}, uri:{}", accessKey, secretKey, uri);
backErrorMessage(response, apiResponseBean);
return;
}
if (StringUtils.isNotBlank(openApiMap.get(uri))) {
// API調用埋點
apiInvokeRecordService.point(uri, openApiMap.get(uri), accessKey);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
/***
* 是否需要鑒權API
* @param uri
* @return
*/
private boolean isApiUri(String uri) {
if (openApiMap.keySet().contains(uri)) {
return true;
}
for (String api : openApiMap.keySet()) {
if (uri.startsWith(api)) {
return true;
}
}
return false;
}
/**
* 是否需要鑒權API
* @param uri
* @return
*/
private boolean isRmiApiUri(String uri) {
if (openApiMap.keySet().contains(uri)) {
return true;
}
for (String api : openApiMap.keySet()) {
if (uri.startsWith(api)) {
return true;
}
}
return false;
}
/**
* 是否白名單API
* @param uri
* @return
*/
private boolean isWhiteApiUri(String uri) {
return WHITE_API_LIST.contains(uri);
}
private void backErrorMessage(HttpServletResponse response, APIResponseBean apiResponseBean) {
response.setContentType("application/json; charset=UTF-8");
try {
response.getWriter().print(JsonUtil.toJsonString(apiResponseBean));
} catch (IOException e) {
e.printStackTrace();
}
}
}
Filter中我們維護了需要鑒權和跳過鑒權的白名單API、當前請求如果需要鑒權,獲取header頭中的ak、sk參數,查詢緩存進行aksk認證,認證通過請求接口。
這里的AK、SK是什么時候生成的、生成規則又是什么?
答:ak、sk盒子實在用戶注冊的時候就為每個用戶分配了(這了設計的不好,造成aksk的浪費,因為大部分用戶其實沒有自己的應用、也沒有api調用的需求,最好是用戶需要的時候自己出發生成按鈕,在生成?。?/strong>
AK、SK在哪里查看?
答:目前在個人中心和開放api服務平臺都可以看到自己的專屬ak、sk:
AKSK生成規則?
答:跟ID生成器一樣,只要保證唯一就ok,我這里用的簡單的uuid
既然ak、sk都是唯一的,為啥需要兩個,只生成一個token不行嗎?
答:也是可以的,能夠做的鑒權就可以。
為什么業界一般的做法都有兩個配對使用的,有的叫ak、有的叫appId其實是一樣的,用來標識一個接入方,而secretKey其實可以有多個的,即一個用戶有多個sk,方便統計、計費等,同時雙重保障API調用更安全。
當前分享的方案是一種靜態sk方案,有同學在接入云或者其他第三方api的時候用到了動態sk(token)的方式,看字面意思,靜態sk指生成一次不會在變化,動態token及沒次調用前需要重新請求獲取一次(一般會有一定的實效性,避免沒次都要獲取造成的資源浪費,或者永久有效失去了動態的特點)
本文章轉載微信公眾號@coderutil技術
GraphQL API滲透測試指南
Python + BaiduTransAPI :快速檢索千篇英文文獻(附源碼)
掌握ChatGPT API集成的方便指南
node.js + express + docker + mysql + jwt 實現用戶管理restful api
nodejs + mongodb 編寫 restful 風格博客 api
表格插件wpDataTables-將 WordPress 表與 Google Sheets API 連接
手把手教你用Python和Flask創建REST API
使用 Django 和 Django REST 框架構建 RESTful API:實現 CRUD 操作
ASP.NET Web API快速入門介紹