• SpringBoot中使用注解方式拦截恶意访问的IP


    前言

    针对有人频繁重复调用接口,进行恶意访问,例如使用爬虫一类,对服务造成巨大负担,所以需要在接口上进行拦截。

    这里使用自定义注解的方式进行拦截,方便重复调用使用。

    我采用的是使用单机SpringBoot + Redis的方式进行处理,没有用分布式锁

    Redis的配置需要自行配置

    一、后端代码

    1. 依赖引入 pom.xml和 yml配置文件中配置Redis

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-data-redisartifactId>
    4. dependency>
    5. <dependency>
    6. <groupId>org.apache.commonsgroupId>
    7. <artifactId>commons-pool2artifactId>
    8. dependency>
    1. spring:
    2. # redis 配置
    3. redis:
    4. # 地址
    5. host: 127.0.0.1
    6. # 端口,默认为6379
    7. port: 6379
    8. # 数据库索引
    9. database: 0
    10. # 密码
    11. password: 123456
    12. # 连接超时时间
    13. timeout: 10s
    14. lettuce:
    15. pool:
    16. # 连接池中的最小空闲连接
    17. min-idle: 0
    18. # 连接池中的最大空闲连接
    19. max-idle: 8
    20. # 连接池的最大数据库连接数
    21. max-active: 8
    22. # #连接池最大阻塞等待时间(使用负值表示没有限制)
    23. max-wait: -1ms

     

    2. redis部分

    Redis工具类

    Redis工具类,方便Redis的基础操作

    1. import org.springframework.beans.factory.annotation.Autowired;
    2. import org.springframework.data.redis.core.BoundSetOperations;
    3. import org.springframework.data.redis.core.HashOperations;
    4. import org.springframework.data.redis.core.RedisTemplate;
    5. import org.springframework.data.redis.core.ValueOperations;
    6. import org.springframework.stereotype.Component;
    7. import java.util.*;
    8. import java.util.concurrent.TimeUnit;
    9. /**
    10. * spring redis 工具类
    11. * @author Xuedi
    12. **/
    13. @SuppressWarnings(value = {"unchecked", "rawtypes"})
    14. @Component
    15. public class Redisutil {
    16. @Autowired
    17. public RedisTemplate redisTemplate;
    18. /**
    19. * 缓存基本的对象,Integer、String、实体类等
    20. *
    21. * @param key 缓存的键值
    22. * @param value 缓存的值
    23. */
    24. public void setCacheObject(final String key, final T value) {
    25. redisTemplate.opsForValue().set(key, value);
    26. }
    27. /**
    28. * 缓存基本的对象,Integer、String、实体类等
    29. *
    30. * @param key 缓存的键值
    31. * @param value 缓存的值
    32. * @param timeout 时间
    33. * @param timeUnit 时间颗粒度
    34. */
    35. public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
    36. redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    37. }
    38. /**
    39. * 设置有效时间
    40. *
    41. * @param key Redis键
    42. * @param timeout 超时时间
    43. * @return true=设置成功;false=设置失败
    44. */
    45. public boolean expire(final String key, final long timeout) {
    46. return expire(key, timeout, TimeUnit.SECONDS);
    47. }
    48. /**
    49. * 设置有效时间
    50. *
    51. * @param key Redis键
    52. * @param timeout 超时时间
    53. * @param unit 时间单位
    54. * @return true=设置成功;false=设置失败
    55. */
    56. public boolean expire(final String key, final long timeout, final TimeUnit unit) {
    57. return redisTemplate.expire(key, timeout, unit);
    58. }
    59. /**
    60. * 获得缓存的基本对象。
    61. *
    62. * @param key 缓存键值
    63. * @return 缓存键值对应的数据
    64. */
    65. public T getCacheObject(final String key) {
    66. ValueOperations operation = redisTemplate.opsForValue();
    67. return operation.get(key);
    68. }
    69. /**
    70. * 删除单个对象
    71. *
    72. * @param key
    73. */
    74. public boolean deleteObject(final String key) {
    75. return redisTemplate.delete(key);
    76. }
    77. /**
    78. * 删除集合对象
    79. *
    80. * @param collection 多个对象
    81. * @return
    82. */
    83. public long deleteObject(final Collection collection) {
    84. return redisTemplate.delete(collection);
    85. }
    86. /**
    87. * 缓存List数据
    88. *
    89. * @param key 缓存的键值
    90. * @param dataList 待缓存的List数据
    91. * @return 缓存的对象
    92. */
    93. public long setCacheList(final String key, final List dataList) {
    94. Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
    95. return count == null ? 0 : count;
    96. }
    97. /**
    98. * 获得缓存的list对象
    99. *
    100. * @param key 缓存的键值
    101. * @return 缓存键值对应的数据
    102. */
    103. public List getCacheList(final String key) {
    104. return redisTemplate.opsForList().range(key, 0, -1);
    105. }
    106. /**
    107. * 缓存Set
    108. *
    109. * @param key 缓存键值
    110. * @param dataSet 缓存的数据
    111. * @return 缓存数据的对象
    112. */
    113. public BoundSetOperations setCacheSet(final String key, final Set dataSet) {
    114. BoundSetOperations setOperation = redisTemplate.boundSetOps(key);
    115. Iterator it = dataSet.iterator();
    116. while (it.hasNext()) {
    117. setOperation.add(it.next());
    118. }
    119. return setOperation;
    120. }
    121. /**
    122. * 获得缓存的set
    123. *
    124. * @param key
    125. * @return
    126. */
    127. public Set getCacheSet(final String key) {
    128. return redisTemplate.opsForSet().members(key);
    129. }
    130. /**
    131. * 缓存Map
    132. *
    133. * @param key
    134. * @param dataMap
    135. */
    136. public void setCacheMap(final String key, final Map dataMap) {
    137. if (dataMap != null) {
    138. redisTemplate.opsForHash().putAll(key, dataMap);
    139. }
    140. }
    141. /**
    142. * 获得缓存的Map
    143. *
    144. * @param key
    145. * @return
    146. */
    147. public Map getCacheMap(final String key) {
    148. return redisTemplate.opsForHash().entries(key);
    149. }
    150. /**
    151. * 往Hash中存入数据
    152. *
    153. * @param key Redis键
    154. * @param hKey Hash键
    155. * @param value 值
    156. */
    157. public void setCacheMapValue(final String key, final String hKey, final T value) {
    158. redisTemplate.opsForHash().put(key, hKey, value);
    159. }
    160. /**
    161. * 获取Hash中的数据
    162. *
    163. * @param key Redis键
    164. * @param hKey Hash键
    165. * @return Hash中的对象
    166. */
    167. public T getCacheMapValue(final String key, final String hKey) {
    168. HashOperations opsForHash = redisTemplate.opsForHash();
    169. return opsForHash.get(key, hKey);
    170. }
    171. /**
    172. * 获取多个Hash中的数据
    173. *
    174. * @param key Redis键
    175. * @param hKeys Hash键集合
    176. * @return Hash对象集合
    177. */
    178. public List getMultiCacheMapValue(final String key, final Collection hKeys) {
    179. return redisTemplate.opsForHash().multiGet(key, hKeys);
    180. }
    181. /**
    182. * 获得缓存的基本对象列表
    183. *
    184. * @param pattern 字符串前缀
    185. * @return 对象列表
    186. */
    187. public Collection keys(final String pattern) {
    188. return redisTemplate.keys(pattern);
    189. }
    190. /**
    191. * 按delta递增
    192. * @param key Redis键
    193. * @param delta xxx
    194. * @return
    195. */
    196. public Long incr(String key, long delta) {
    197. return redisTemplate.opsForValue().increment(key, delta);
    198. }
    199. }
    200. RedisTemplateConfig配置类

      添加这个配置类,防止存Redis的时候key或value乱码

      1. import com.fasterxml.jackson.annotation.JsonAutoDetect;
      2. import com.fasterxml.jackson.annotation.PropertyAccessor;
      3. import com.fasterxml.jackson.databind.ObjectMapper;
      4. import org.springframework.context.annotation.Bean;
      5. import org.springframework.context.annotation.Configuration;
      6. import org.springframework.data.redis.connection.RedisConnectionFactory;
      7. import org.springframework.data.redis.core.*;
      8. import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
      9. import org.springframework.data.redis.serializer.StringRedisSerializer;
      10. /**
      11. * redis配置 防止存redis时乱码
      12. */
      13. @Configuration
      14. public class RedisTemplateConfig{
      15. /**
      16. * retemplate相关配置
      17. * @param factory
      18. * @return
      19. */
      20. @Bean
      21. public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
      22. RedisTemplate template = new RedisTemplate<>();
      23. // 配置连接工厂
      24. template.setConnectionFactory(factory);
      25. //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
      26. Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
      27. ObjectMapper om = new ObjectMapper();
      28. // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
      29. om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
      30. // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
      31. om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
      32. jacksonSeial.setObjectMapper(om);
      33. // 值采用json序列化
      34. template.setValueSerializer(jacksonSeial);
      35. //使用StringRedisSerializer来序列化和反序列化redis的key值
      36. template.setKeySerializer(new StringRedisSerializer());
      37. // 设置hash key 和value序列化模式
      38. template.setHashKeySerializer(new StringRedisSerializer());
      39. template.setHashValueSerializer(jacksonSeial);
      40. template.afterPropertiesSet();
      41. return template;
      42. }
      43. /**
      44. * 对hash类型的数据操作
      45. *
      46. * @param redisTemplate
      47. * @return
      48. */
      49. @Bean
      50. public HashOperations hashOperations(RedisTemplate redisTemplate) {
      51. return redisTemplate.opsForHash();
      52. }
      53. /**
      54. * 对redis字符串类型数据操作
      55. *
      56. * @param redisTemplate
      57. * @return
      58. */
      59. @Bean
      60. public ValueOperations valueOperations(RedisTemplate redisTemplate) {
      61. return redisTemplate.opsForValue();
      62. }
      63. /**
      64. * 对链表类型的数据操作
      65. *
      66. * @param redisTemplate
      67. * @return
      68. */
      69. @Bean
      70. public ListOperations listOperations(RedisTemplate redisTemplate) {
      71. return redisTemplate.opsForList();
      72. }
      73. /**
      74. * 对无序集合类型的数据操作
      75. *
      76. * @param redisTemplate
      77. * @return
      78. */
      79. @Bean
      80. public SetOperations setOperations(RedisTemplate redisTemplate) {
      81. return redisTemplate.opsForSet();
      82. }
      83. /**
      84. * 对有序集合类型的数据操作
      85. *
      86. * @param redisTemplate
      87. * @return
      88. */
      89. @Bean
      90. public ZSetOperations zSetOperations(RedisTemplate redisTemplate) {
      91. return redisTemplate.opsForZSet();
      92. }
      93. }

      3. 接口返回结果封装对象

      1. import java.util.HashMap;
      2. public class Result extends HashMap {
      3. private static final long serialVersionUID = 1L;
      4. /**
      5. * 状态码
      6. */
      7. public static final String CODE_TAG = "code";
      8. /**
      9. * 返回内容
      10. */
      11. public static final String MSG_TAG = "msg";
      12. /**
      13. * 数据对象
      14. */
      15. public static final String DATA_TAG = "data";
      16. /**
      17. * 初始化一个新创建的 Result 对象,使其表示一个空消息。
      18. */
      19. public Result() {
      20. }
      21. /**
      22. * 初始化一个新创建的 Result 对象
      23. *
      24. * @param code 状态码
      25. * @param msg 返回内容
      26. */
      27. public Result(int code, String msg) {
      28. super.put(CODE_TAG, code);
      29. super.put(MSG_TAG, msg);
      30. }
      31. /**
      32. * 初始化一个新创建的 Result 对象
      33. *
      34. * @param code 状态码
      35. * @param msg 返回内容
      36. * @param data 数据对象
      37. */
      38. public Result(int code, String msg, Object data) {
      39. super.put(CODE_TAG, code);
      40. super.put(MSG_TAG, msg);
      41. if (data != null) {
      42. super.put(DATA_TAG, data);
      43. }
      44. }
      45. /**
      46. * 返回成功消息
      47. *
      48. * @return 成功消息
      49. */
      50. public static Result success() {
      51. return Result.success("操作成功");
      52. }
      53. /**
      54. * 返回成功数据
      55. *
      56. * @return 成功消息
      57. */
      58. public static Result success(Object data) {
      59. return Result.success("操作成功", data);
      60. }
      61. /**
      62. * 返回成功消息
      63. *
      64. * @param msg 返回内容
      65. * @return 成功消息
      66. */
      67. public static Result success(String msg) {
      68. return Result.success(msg, null);
      69. }
      70. /**
      71. * 返回成功消息
      72. *
      73. * @param msg 返回内容
      74. * @param data 数据对象
      75. * @return 成功消息
      76. */
      77. public static Result success(String msg, Object data) {
      78. return new Result(200, msg, data);
      79. }
      80. /**
      81. * 返回错误消息
      82. *
      83. * @return
      84. */
      85. public static Result error() {
      86. return Result.error("操作失败");
      87. }
      88. /**
      89. * 返回错误消息
      90. *
      91. * @param msg 返回内容
      92. * @return 警告消息
      93. */
      94. public static Result error(String msg) {
      95. return Result.error(msg, null);
      96. }
      97. /**
      98. * 返回错误消息
      99. *
      100. * @param msg 返回内容
      101. * @param data 数据对象
      102. * @return 警告消息
      103. */
      104. public static Result error(String msg, Object data) {
      105. return new Result(500, msg, data);
      106. }
      107. /**
      108. * 返回错误消息
      109. *
      110. * @param code 状态码
      111. * @param msg 返回内容
      112. * @return 警告消息
      113. */
      114. public static Result error(int code, String msg) {
      115. return new Result(code, msg, null);
      116. }
      117. /**
      118. * 方便链式调用
      119. *
      120. * @param key 键
      121. * @param value 值
      122. * @return 数据对象
      123. */
      124. @Override
      125. public Result put(String key, Object value) {
      126. super.put(key, value);
      127. return this;
      128. }
      129. }

      4. 拦截部分

      自定义注解

      1. import java.lang.annotation.*;
      2. import java.util.concurrent.TimeUnit;
      3. /**
      4. * 自定义注解 - 拦截恶意访问接口
      5. * 这里的默认值,可以根据自己需求修改
      6. * @author xuedi
      7. */
      8. @Target({ElementType.METHOD, ElementType.TYPE})
      9. @Retention(RetentionPolicy.RUNTIME)
      10. @Documented
      11. public @interface AccessLimit {
      12. /**
      13. * 时间限制大小
      14. */
      15. long seconds() default 60;
      16. /**
      17. * 最大次数
      18. */
      19. long maxCount() default 100;
      20. /**
      21. * 时间单位
      22. */
      23. TimeUnit timeUnit() default TimeUnit.SECONDS;
      24. /**
      25. * 接口名称
      26. */
      27. String apiName() default "";
      28. }

      拦截恶意访问接口 - 逻辑处理

      1. import com.alibaba.fastjson.JSONObject;
      2. import com.smart.soulsoup.utils.commons.Result;
      3. import com.smart.soulsoup.utils.redis.RedisCache;
      4. import org.springframework.beans.factory.annotation.Autowired;
      5. import org.springframework.stereotype.Component;
      6. import org.springframework.web.method.HandlerMethod;
      7. import org.springframework.web.servlet.HandlerInterceptor;
      8. import javax.servlet.http.HttpServletRequest;
      9. import javax.servlet.http.HttpServletResponse;
      10. import java.util.concurrent.TimeUnit;
      11. /**
      12. * 拦截恶意访问接口 - 逻辑处理
      13. */
      14. @Component
      15. public class BrushProofHandler implements HandlerInterceptor {
      16. @Autowired
      17. RedisCache redisCache;
      18. private static final String REDIS_KEY_PREFIX= "api::interface";
      19. @Override
      20. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      21. // 判断是不是方法
      22. if (handler instanceof HandlerMethod) {
      23. HandlerMethod method = (HandlerMethod) handler;
      24. // 判断方法中是否有此注解
      25. AccessLimit accessLimit = method.getMethodAnnotation(AccessLimit.class);
      26. if (accessLimit != null) {
      27. // 时间限制大小
      28. long seconds = accessLimit.seconds();
      29. // 最大次数
      30. long maxCount = accessLimit.maxCount();
      31. // 时间单位
      32. TimeUnit timeUnit = accessLimit.timeUnit();
      33. // 接口名称
      34. String apiName = accessLimit.apiName();
      35. // 获取调用者ip
      36. String ip = request.getHeader("x-forwarded-for");
      37. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
      38. ip = request.getHeader("Proxy-Client-IP");
      39. }
      40. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
      41. ip = request.getHeader("WL-Proxy-Client-IP");
      42. }
      43. if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
      44. ip = request.getRemoteAddr();
      45. }
      46. // redis的完整key
      47. String redisKey = REDIS_KEY_PREFIX + "::" + apiName + ":: " + ip;
      48. Integer count = (Integer) redisCache.getCacheObject(redisKey);
      49. if (count == null) {
      50. redisCache.setCacheObject(redisKey, 1, Integer.valueOf(String.valueOf(seconds)), timeUnit);
      51. } else if (count < maxCount) {
      52. // 在时间允许范围内
      53. redisCache.incr(redisKey, 1);
      54. } else {
      55. response.setCharacterEncoding("UTF-8");
      56. response.setContentType("application/json; charset=utf-8");
      57. Result result = Result.error("请你注意你的行为!不要频繁访问!");
      58. response.getWriter().write(JSONObject.toJSONString(result));
      59. return false;
      60. }
      61. }
      62. }
      63. return true;
      64. }
      65. }

      拦截恶意访问接口配置 

      1. import org.springframework.beans.factory.annotation.Autowired;
      2. import org.springframework.context.annotation.Configuration;
      3. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
      4. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
      5. /**
      6. * 拦截恶意访问接口配置
      7. */
      8. @Configuration
      9. public class WebConfig implements WebMvcConfigurer {
      10. @Autowired
      11. private BrushProofHandler brushProofHandler;
      12. @Override
      13. public void addInterceptors(InterceptorRegistry registry) {
      14. registry.addInterceptor(brushProofHandler);
      15. }
      16. }

      5. Controller接口

      1. import com.smart.demolsoup.service.ISoulService;
      2. import com.smart.demo.utils.api.AccessLimit;
      3. import org.springframework.stereotype.Controller;
      4. import org.springframework.web.bind.annotation.*;
      5. import java.util.concurrent.TimeUnit;
      6. @Controller
      7. public class TestController {
      8. /**
      9. * 测试
      10. * 入参可以参考AccessLimit.java
      11. **/
      12. @AccessLimit(seconds = 30, maxCount = 150, apiName = "getStr", timeUnit = TimeUnit.MINUTES)
      13. @ResponseBody
      14. @GetMapping("/getStr”)
      15. public String getStr() {
      16. return “SUCCESS;
      17. }
      18. }

      二、测试

    201. 相关阅读:
      【快应用】如何使用命令打包快应用rpk
      基于python的毕业设计本地健康宝微信小程序
      idea报错“Static methods in interface require -target:jvm-1.8”
      GitHub与GitHubDesktop的使用
      信息学奥赛一本通(c++):1128:图像模糊处理
      2022腾讯全球数字生态大会【存储专场】它来了|预约有礼
      python基础语法(二)
      kotlin 音频播放,多音轨同时播放,音频播放期间,可以随时设置播放速度
      Linux下,使用nm命令输出可执行文件的符号表
      PCL库常用算法
    202. 原文地址:https://blog.csdn.net/Eternal_Blue/article/details/126171700