在实际项目开发部署过程中,我们需要保证服务的安全性和可用性,当项目部署到服务器后,就要考虑服务被恶意请求和暴力攻击的情况。如何防止我们对外的接口被暴力攻击?下面的教程,通过Springboot提供的拦截器和Redis 针对IP在一定时间内访问的次数来将IP禁用,拒绝服务
1、新建RedisUtil
RedisUtil用于缓存数据
@Component
public class RedisUtil {@Resourceprivate StringRedisTemplate stringRedisTemplate;/*** 取值* @param key* @return*/public String get(final String key) {return stringRedisTemplate.opsForValue().get(key);}/*** 是否存在键* @param key* @return*/public boolean hasKey(final String key){return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key));}/*** 根据前缀正则获取所有键* @param prefix* @return*/public Set<String> getKeysByPattern(final String prefix){Set<String> keys = this.getKeys(prefix.concat("*"));return keys;}private Set<String> getKeys(String pattern){Set<String> keys = stringRedisTemplate.execute((RedisCallback<Set<String>>) connection -> {Set<String> keysTmp = new HashSet<>();Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(pattern).count(1000).build());while (cursor.hasNext()) {keysTmp.add(new String(cursor.next()));}return keysTmp;});return keys;}/*** 设值,时间单位为秒* @param key* @param value* @param timeout* @return*/public boolean set(final String key, String value, long timeout, TimeUnit timeUnit) {boolean result = false;try {stringRedisTemplate.opsForValue().set(key, value, timeout, timeUnit);result = true;} catch (Exception e) {e.printStackTrace();}return result;}/*** 删除一个键值* @param key* @return*/public boolean delete(final String key) {boolean result = false;try {stringRedisTemplate.delete(key);result = true;} catch (Exception e) {e.printStackTrace();}return result;}/*** 批量删除键值* @param keys* @return*/public boolean delete(Collection<String> keys) {boolean result = false;try {stringRedisTemplate.delete(keys);result = true;} catch (Exception e) {e.printStackTrace();}return result;}/*** 自增* @param key* @param value* @param timeout* @param timeUnit*/public void increment(String key, String value, long timeout, TimeUnit timeUnit){if(hasKey(key)){stringRedisTemplate.opsForValue().increment(key, Long.parseLong(value));}else {set(key, value, timeout, timeUnit);}}/*** 根据正则表达式删除所有键值* @param pattern* @return*/public boolean deleteAllByPattern(final String pattern){Set<String> keysByPattern = getKeysByPattern(pattern);return delete(keysByPattern);}
}
2、新建IPUtil
IPUtil用于获取客户端请求的IP
@Component
public class IPUtil {public String getIpAddr(HttpServletRequest request) {String ipAddress;try {ipAddress = request.getHeader("x-forwarded-for");if (ipAddress == null || ipAddress.length() == 0|| "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0|| "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getHeader("WL-Proxy-Client-IP");}if (ipAddress == null || ipAddress.length() == 0|| "unknown".equalsIgnoreCase(ipAddress)) {ipAddress = request.getRemoteAddr();if (ipAddress.equals("127.0.0.1")) {// 根据网卡取本机配置的IPInetAddress inet = null;try {inet = InetAddress.getLocalHost();} catch (UnknownHostException e) {e.printStackTrace();}ipAddress = inet.getHostAddress();}}// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()// = 15if (ipAddress.indexOf(",") > 0) {ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));}}} catch (Exception e) {ipAddress = "";}return ipAddress;}
}
3、定义拦截器
拦截器代码如下,该拦截器将记录一天内的单个IP的请求数量,当请求数量达到1000时,不再处理请求,直接响应报错
@Component
public class ViolentRequestInterceptor implements HandlerInterceptor {//配置文件配置最大请求数量//@Value("${violentRequest.maxCount}")//private int maxCount;private final int maxCount = 1000;@Autowiredprivate RedisUtil redisUtil;@Autowiredprivate IPUtil ipUtil;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String ipAddr = ipUtil.getIpAddr(request);String key = ConstUtil.SYS_PREVENT_VIOLENT_REQUESTS + ipAddr;if(!redisUtil.hasKey(key)){redisUtil.increment(key, String.valueOf(1), 1, TimeUnit.DAYS);}else {long count = Long.parseLong(redisUtil.get(key));if(count>maxCount){JSONObject json = (JSONObject) JSONObject.toJSON(Result.failure(HAVIOR_INVOKE_ERROR));response.setContentType("application/json;charset=UTF-8");response.getWriter().println(json);return false;}redisUtil.increment(key, String.valueOf(1), 1, TimeUnit.DAYS);}return true;}
}
4、配置拦截器
配置拦截器对所有请求生效并设置优先级为1
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Autowiredprivate ViolentRequestInterceptor violentRequestInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(violentRequestInterceptor).addPathPatterns("/**").order(1);}