001package top.cenze.utils.plugins; 002 003import cn.hutool.core.collection.CollectionUtil; 004import cn.hutool.core.convert.Convert; 005import cn.hutool.core.util.ObjectUtil; 006import cn.hutool.core.util.StrUtil; 007import com.alibaba.fastjson.JSON; 008import lombok.SneakyThrows; 009import lombok.extern.slf4j.Slf4j; 010import top.cenze.utils.TokenUtil; 011import top.cenze.utils.aspect.AspectUtil; 012import top.cenze.utils.pojo.ApiAuthConfig; 013 014import javax.servlet.http.HttpServletRequest; 015import java.util.HashSet; 016import java.util.Set; 017 018/** 019 * @desc: 接口插件 020 * @author: chengze 021 * @createByDate: 2023/12/30 18:25 022 */ 023@Slf4j 024public abstract class ApiPlugin { 025 026 /** 027 * 开放接口访问缓存前缀 028 */ 029 private static final String API_ACCESS_PREFIX = "API_ACCESS:"; 030 031 /** 032 * 接口是否开放的,忽略大小写,返回String(无需登录即可访问,允许三方系统直接访问) 033 * @param requestUri 当前请求的uri (ServletPath) 034 * @return 返回第一个被请求uri包含的接口uri 035 */ 036 public String isOpenStrIgnoreCase(String requestUri) { 037 // 获取开放接口uri集合 038 Set<String> setkeys = getOpenApiUris(); 039 log.info("isOpenStrIgnoreCase openApis: {}", JSON.toJSONString(setkeys)); 040 041 // 接口关键字集合为空时,则赋一个空值 042 String[] keys = null; 043 if (CollectionUtil.isEmpty(setkeys)) { 044 setkeys = new HashSet<>(); 045 setkeys.add(""); 046 keys = new String[] {""}; 047 } else { 048 keys = Convert.toStrArray(setkeys); 049 } 050 051 // 请求uri是否包含接口关键字集合中的成员(忽略大小写),返回集合中uri包含的第一个字符串 052 return StrUtil.getContainsStrIgnoreCase(requestUri, keys); 053 } 054 055 /** 056 * 接口是否开放的,忽略大小写,返回Boolean(无需登录即可访问,允许三方系统直接访问) 057 * @param requestUri 当前请求的uri (ServletPath) 058 * @return 返回是否为开放接口,true是,false否 059 */ 060 public boolean isOpenIgnoreCase(String requestUri) { 061 String containsStr = isOpenStrIgnoreCase(requestUri); 062 log.info("isOpenIgnoreCase containsStr: {}", containsStr); 063 064 if (StrUtil.isNotEmpty(containsStr)) { 065 return true; 066 } 067 068 return false; 069 } 070 071 /** 072 * 获取开放接口uri列表 073 * @return 074 */ 075 public abstract Set<String> getOpenApiUris(); 076 077 /** 078 * 接口鉴权,返回Boolean 079 * @param request servlet请求 080 * @return 返回鉴权结果,通过true,未通过false 081 */ 082 public boolean auth(HttpServletRequest request) { 083 return auth(request, false); 084 } 085 /** 086 * 接口鉴权,返回Boolean 087 * @param request servlet请求 088 * @param isAop 是否为注解切入 089 * @return 返回鉴权结果,通过true,未通过false 090 */ 091 @SneakyThrows 092 public boolean auth(HttpServletRequest request, boolean isAop) { 093 // 获取系统配置的开放接口中匹配当前请求uri的接口 094 String openApiUri = isOpenStrIgnoreCase(request.getServletPath()); 095 if (StrUtil.isEmpty(openApiUri)) { 096 // 如果是注解切入,则鉴权不通过(严格限定) 097 if (isAop) { 098 return false; 099 } 100 // 不为注解切入,当前访问非开放接口,则忽略鉴权 101 else { 102 return true; 103 } 104 } 105 106 ApiAuthConfig apiAuthConfig = getApiAuthConfig(openApiUri); 107 log.info("auth apiAuthConfig: {}", JSON.toJSONString(apiAuthConfig)); 108 // 鉴权配置为空,或不需要鉴权,则鉴权通过 109 if (ObjectUtil.isNull(apiAuthConfig) || !apiAuthConfig.getRequireAuth().equals(1)) { 110 return true; 111 } 112 113 String token = TokenUtil.resolveBearerTokenFromRequest(request); 114 log.info("auth token: {}", token); 115 116 String appSecret = getAuthSecret(token); 117 log.info("auth appSecret: {}", appSecret); 118 119 return TokenUtil.validBearerToken(token, appSecret); 120 } 121 122 /** 123 * 获取token的访问密码 124 * @param token 125 * @return 126 */ 127 public abstract String getAuthSecret(String token); 128 129 /** 130 * 是否超过访问限制次数(配置的限制次数,限制时间范围,单位分钟) 131 * @param request 访问请求 servlet请求 132 * @return 超过true,未超过false 133 */ 134 public boolean exceedLimitCount(HttpServletRequest request) { 135 // 获取系统配置的开放接口中匹配当前请求uri的接口 136 String openApiUri = isOpenStrIgnoreCase(request.getServletPath()); 137 if (StrUtil.isEmpty(openApiUri)) { 138 return false; 139 } 140 141 String key = API_ACCESS_PREFIX + AspectUtil.getIpAddress(request) + "_" + openApiUri; 142 log.info("exceedLimitCount key: {}", key); 143 144 ApiAuthConfig apiAuthConfig = getAccessApiAuthConfig(key); 145 log.info("exceedLimitCount ip apiAuthConfig: {}", JSON.toJSONString(apiAuthConfig)); 146 // ip访问鉴权配置为空,则系统公共配置 147 if (ObjectUtil.isNull(apiAuthConfig)) { 148 apiAuthConfig = getApiAuthConfig(openApiUri); 149 log.info("exceedLimitCount apiAuthConfig: {}", JSON.toJSONString(apiAuthConfig)); 150 } 151 // 鉴权配置为空,则不限制 152 if (ObjectUtil.isNull(apiAuthConfig)) { 153 return false; 154 } 155 156 // 配置的限制次数(0~65535),0不限制 157 Integer limitCount = apiAuthConfig.getLimitCount(); 158 log.info("exceedLimitCount limitCount: {}", limitCount); 159 if (ObjectUtil.isNull(limitCount)) { 160 limitCount = 0; 161 } 162 // 配置的限制时间范围(0~ 4294967295,单位:分钟),0不限制(多少分钟内限制访问多少次,limit_count或limit_time为0时,不限制) 163 Integer limitTime = apiAuthConfig.getLimitTime(); 164 log.info("exceedLimitCount limitTime: {}", limitTime); 165 if (ObjectUtil.isNull(limitTime)) { 166 limitTime = 0; 167 } 168 169 // 如果设置了访问次数限制(设置了限制次数及时间范围,且设置值均大于0) 170 if (ObjectUtil.isNull(limitCount) || ObjectUtil.isNull(limitCount) || 171 limitCount <= 0 || limitTime <= 0) { 172 return false; 173 } 174 175 // 当前时间戳,单位:分钟 176 Integer curTime = Convert.toInt(System.currentTimeMillis() / 60000); 177 log.info("exceedLimitCount curTime: {}", curTime); 178 179 // 访问次数 180 Integer accessCount = apiAuthConfig.getAccessCount(); 181 log.info("exceedLimitCount accessCount: {}", accessCount); 182 if (ObjectUtil.isNull(accessCount)) { 183 accessCount = 0; 184 } 185 // 获取当前接口时间范围内初始时间戳,单位:分钟 186 Integer accessTime = apiAuthConfig.getAccessFirstTime(); 187 log.info("exceedLimitCount accessTime: {}", accessTime); 188 if (ObjectUtil.isNull(accessTime) || accessTime.equals(0)) { 189 accessTime = curTime; 190 apiAuthConfig.setAccessFirstTime(curTime); 191 } 192 193 // 计算当前限制时间范围内已过去时间,单位分钟 194 int pastTime = curTime - accessTime; 195 log.info("exceedLimitCount pastTime: {}", pastTime); 196 if (pastTime >= limitTime) { 197 // 重置访问次数和时间 198 apiAuthConfig.setAccessCount(0); 199 apiAuthConfig.setAccessFirstTime(0); 200 201 accessCount = 0; 202 } 203 204 accessCount ++; 205 // 更新缓存访问次数 206 apiAuthConfig.setAccessCount(accessCount); 207 // 更新缓存 208 setAccessApiAuthConfig(key, apiAuthConfig); 209 210 // 超过限制次数 211 if (accessCount > limitCount) { 212 return true; 213 } 214 215 return false; 216 } 217 218 /** 219 * 获取授权配置 220 * @param apiUri 221 */ 222 public abstract ApiAuthConfig getApiAuthConfig(String apiUri); 223 224 /** 225 * 获取各ip访问授权配置 226 * @param key 227 */ 228 public abstract ApiAuthConfig getAccessApiAuthConfig(String key); 229 230 /** 231 * 缓存各ip访问授权配置 232 * @param key 233 * @param authConfig 234 */ 235 public abstract void setAccessApiAuthConfig(String key, ApiAuthConfig authConfig); 236}