安全——登陆接口的设计
没有绝对的安全,只的一步一步的防护
登陆是系统的门户接口,也是最容易被访问的接口。你写的是合格的吗,还是只是实现了个查询。
一、相关库表的设计
1、用户的基本信息和用户账号信息最好没有放在一个表;
2、有没有三方登陆的需求,有的话;第三方的信息最好在一个表;
3、密码加密是肯定的,那加盐了吗,账号信息表中放一个salt吧;
4、做上登陆记录Log表,也可以记录ip可以做异地提醒;
二、你的友好提示可能存在安全问题
1、账号不存在?表示你做了一个全表的账号查询。
2、密码错误?表示我的账号是对的了?那我试试这个密码?
* 账号或密码错误。这是可取的。
三、做一个防暴力破解吧
传统用户名,密码登陆
例子:登陆接口,一分钟内出错超过3次,账号封锁3分钟,并向用户手机发送验证和提醒。具体业务逻辑可以自定义;
实现:springboot的aop+redis
aop的两个方法:
@AfterReturning :后置——接口方法执行完
@AfterThrowing:存在异常——接口方法执行存在异常
基本逻辑:账号失败一次,就把账号做为key,vaue为 0,time=过期时间放到redis里;失败一次+1;成功:看看redis里有没有这个key,有就删除;
redis.setString(key:用户名或账号,value: 次数,time:(过期时间))
核心代码:
@AfterReturning(returning = "ret", pointcut = "countPoint()")// returning的值和doAfterReturning的参数名一致
public void doAfterReturning(Object ret) throws Throwable {
ObjectMapper objectMapper = new ObjectMapper();
// ResponseVO responseVO = objectMapper.convertValue(ret, ResponseVO.class);
String s = redisUtils.get(redisKey);
if (s != null) {
redisUtils.del(redisKey);
}
}
@AfterThrowing(throwing = "ex", pointcut = "countPoint()")
public void afterThrowing(Exception ex) {
String s = redisUtils.get(redisKey);
if (null == s) {
redisUtils.set(redisKey, "1", defaultTimeOut);
} else {
Integer i = Integer.parseInt(s) + 1;
if (i < count) {//小于设定次数
long ttl = redisUtils.ttl(redisKey);
redisUtils.set(redisKey, i.toString(), ttl);
}
if (i == count) {//符合设定值
redisUtils.set(redisKey, i.toString(), timeOut);
throw new EchoException("请求锁定," + timeOut + "秒后再做尝试。");
}
}
}
完整代码
1、自定义注解
/**
* @Description: 多次接口尝试进行账户封锁
* @Author: HuangJiangMin
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodResponseException {
/**
* 失敗次数
*/
int exceptionCount() default 5;
/**
* 限制时间 (秒)
*/
long redisExpired() default 60;
}
2、AOP
/**
* @Description:多次接口尝试进行账户封锁
* @Author:HuangJiangMin
*/
@Aspect
@Component
public class ResponseExceptionAspect {
@Autowired
private RedisUtils redisUtils;
private String redisKey;
private int count;
private long timeOut;
private long defaultTimeOut = 60;
@Pointcut("@annotation(scirichon.echo.art.infrastructure.annotation.MethodResponseException)")
private void countPoint() {
}
@Before("countPoint()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String methodUrl = request.getRequestURL().toString();
/**
* 获取注解自定义参数
*/
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
MethodResponseException methodResponseException = method.getAnnotation(MethodResponseException.class);
count = methodResponseException.exceptionCount();
timeOut = methodResponseException.redisExpired();
redisKey = RedisEnum.RESPONSE_EXCEPTION_COUNT_KEY.tacetion(methodKey);
String s = redisUtils.get(redisKey);
if (null != s && count == Integer.parseInt(s)) {
throw new EchoException("请求锁定中,请稍后再做尝试。");
}
}
@AfterReturning(returning = "ret", pointcut = "countPoint()")// returning的值和doAfterReturning的参数名一致
public void doAfterReturning(Object ret) throws Throwable {
ObjectMapper objectMapper = new ObjectMapper();
// ResponseVO responseVO = objectMapper.convertValue(ret, ResponseVO.class);
String s = redisUtils.get(redisKey);
if (s != null) {
redisUtils.del(redisKey);
}
}
@AfterThrowing(throwing = "ex", pointcut = "countPoint()")
public void afterThrowing(Exception ex) {
String s = redisUtils.get(redisKey);
if (null == s) {
redisUtils.set(redisKey, "1", defaultTimeOut);
} else {
Integer i = Integer.parseInt(s) + 1;
if (i < count) {//小于设定次数
long ttl = redisUtils.ttl(redisKey);
redisUtils.set(redisKey, i.toString(), ttl);
}
if (i == count) {//符合设定值
redisUtils.set(redisKey, i.toString(), timeOut);
throw new EchoException("请求锁定," + timeOut + "秒后再做尝试。");
}
}
}
@Around("countPoint()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long startTime = System.currentTimeMillis();
Object ob = pjp.proceed();// ob 为方法的返回值
//logger.info("性能监控(耗时) : " + (System.currentTimeMillis() - startTime) + "毫秒");
return ob;
}
}
以上就用springboot实现了防暴力破解
安全只是相对的。
有人说了,我可以写个程序一直调用你的接口;让所有的用户都登陆不上。
——用CSRF可以;
又有问题了 referer可以人为变更。你要怎么处理呢。
——一个IP一分钟内访问超过多少次,就封禁可以吗(这个不一定可行)
只有层层防护。没有绝对安全
我的个人博客:
http://www.dhzi.com.cn/