简单的操作多次后输入验证码


发表于 2017-04-12 20:58


需求就是短时间内多次登录尝试之后再次尝试需要输入验证码,这里用基于内存判断的方法实现下:

因为我是单用户博客,所以增加了一个总尝试数目,用于防止基于不断变换ip的攻击

package me.qyh.blog.core.security;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import me.qyh.blog.core.exception.SystemException;

/**
 * 用来判断是否需要输入验证码
 * <p>
 * 当某个ip尝试此时达到attemptCount的情况下,如果该ip继续尝试,则需要输入验证码<br>
 * 当尝试总数达到maxAttemptCount的情况下,如果有任何ip继续尝试,则需要输入验证码<br>
 * </p>
 * 基本用法:
 * 
 * <pre>
 * AttemptLogger logger = new AttemptLogger(10,100);
 * if(logger.log(ip)){
 *   //判断验证码是否正确
 * }
 * 
 * reach(ip) 
 * //判断某个ip是否需要输入验证码
 * </pre>
 * 
 * @author Administrator
 *
 */
public class AttemptLogger {

	private final int attemptCount;
	private final int maxAttemptCount;
	private final Map<String, AtomicInteger> map = new ConcurrentHashMap<>();
	private final AtomicInteger maxAttemptCounter;

	private final ScheduledExecutorService ses;

	public AttemptLogger(int attemptCount, int maxAttemptCount, int sec) {
		super();
		if (attemptCount < 1) {
			throw new SystemException("尝试次数不能小于1");
		}
		this.attemptCount = attemptCount;
		this.maxAttemptCount = maxAttemptCount;
		this.maxAttemptCounter = new AtomicInteger(0);
		ses = Executors.newSingleThreadScheduledExecutor();

		ses.scheduleWithFixedDelay(this::clear, 0, sec, TimeUnit.SECONDS);
	}

	/**
	 * 尝试,次数+1
	 * 
	 * @param t
	 * @return 如果返回true,则说明达到阈值
	 */
	public boolean log(String t) {
		Objects.requireNonNull(t);
		BooleanHolder holder = new BooleanHolder(true);
		map.compute(t, (k, v) -> {
			if (v == null && maxAttemptCounter.get() < maxAttemptCount) {
				v = new AtomicInteger();
			}
			if (v != null) {
				holder.value = add(v);
			}
			return v;
		});
		return holder.value;
	}

	/**
	 * 是否达到阈值
	 * 
	 * @param t
	 * @return
	 */
	public boolean reach(String t) {
		if (maxAttemptCounter.get() == maxAttemptCount) {
			return true;
		}
		AtomicInteger counter = map.get(t);
		return counter != null && counter.get() == attemptCount;
	}

	private boolean add(AtomicInteger v) {
		int count = v.get();
		if (count == attemptCount) {
			return true;
		}
		for (;;) {
			int maxCount = maxAttemptCounter.get();
			if (maxCount == maxAttemptCount) {
				return true;
			}
			if (maxAttemptCounter.compareAndSet(maxCount, maxCount + 1)) {
				v.incrementAndGet();
				return false;
			}
		}
	}

	/**
	 * 移除一个记录
	 * 
	 * @param t
	 */
	public void remove(String t) {
		map.computeIfPresent(t, (k, v) -> {
			maxAttemptCounter.addAndGet(-v.get());
			return null;
		});
	}

	public void close() {
		this.ses.shutdownNow();
	}

	private void clear() {
		map.keySet().forEach(this::remove);
	}

	private final class BooleanHolder {
		private boolean value;

		public BooleanHolder(boolean value) {
			super();
			this.value = value;
		}
	}
}

基本用法就是

String ip = getIp(request);
if (attemptLogger.log(ip)){
	//需要验证码判断    
}

if(attemptLogger.reach(ip)){
	//需要验证码判断
}

搜索