Commit 7920a055 by tntxia

登录错误次数限制和登录日志

parent 8eab34eb
......@@ -110,6 +110,45 @@ public class RedisOpsUtils {
return redisTemplate.opsForValue().get(key);
}
/**
* GET Integer
*
* @param key key
* @return result
*/
public static Integer getInt(String key) {
Object obj = get(key);
if (obj == null) {
return 0;
}
if (obj instanceof Integer) {
return (Integer) obj;
}
if (obj instanceof Long) {
return ((Long) obj).intValue();
}
if (obj instanceof Double) {
return ((Double) obj).intValue();
}
if (obj instanceof Float) {
return ((Float) obj).intValue();
}
if (obj instanceof Boolean) {
Boolean bool = (Boolean) obj;
return bool ? 1 : 0;
}
return Integer.parseInt(obj.toString());
}
/**
* SETEX
* @param key
......
package com.dji.sample.manage.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dji.sample.manage.model.entity.LoginLogEntity;
public interface ILoginLogMapper extends BaseMapper<LoginLogEntity> {
}
package com.dji.sample.manage.model.dto;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
@Builder
@Data
public class LoginLogDTO implements Serializable {
private Integer id;
private String userId;
private String username;
private String message;
private boolean success;
private String ipAddress;
private String userAgent;
private Long createTime;
}
package com.dji.sample.manage.model.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serializable;
@TableName(value = "login_log")
@Data
public class LoginLogEntity implements Serializable {
@TableId(type = IdType.AUTO)
private Integer id;
@TableField(value = "user_id")
private String userId;
@TableField(value = "username")
private String username;
@TableField(value = "message")
private String message;
@TableField(value = "success")
private boolean success;
@TableField(value = "ip_address")
private String ipAddress;
@TableField(value = "user_agent")
private String userAgent;
@TableField(value = "create_time", fill = FieldFill.INSERT)
private Long createTime;
}
......@@ -42,4 +42,14 @@ public class UserEntity implements Serializable {
@TableField(value = "role_type")
private Integer roleType;
@TableField(value = "login_try_count")
private Integer loginTryCount;
@TableField(value = "new_add")
private Boolean newAdd;
@TableField(value = "last_change_password")
private Long lastChangePassword;
}
package com.dji.sample.manage.model.param.searchParam;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* @author chenshixian
**/
@Data
public class LoginLogSearchParam {
@JsonProperty("userId")
private String userId;
@JsonProperty("startDate")
private String startDate;
@JsonProperty("endDate")
private String endDate;
}
package com.dji.sample.manage.model.param.searchParam;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* @author chenshixian
**/
@Data
public class OperateLogSearchParam {
@JsonProperty("userId")
private String userId;
@JsonProperty("operateType")
private String operateType;
@JsonProperty("startDate")
private String startDate;
@JsonProperty("endDate")
private String endDate;
@JsonProperty("deviceSn")
private String deviceSn;
}
package com.dji.sample.manage.service;
import com.dji.sample.manage.model.dto.LoginLogDTO;
import com.dji.sample.manage.model.entity.UserEntity;
import com.dji.sample.manage.model.param.searchParam.LoginLogSearchParam;
import com.dji.sdk.common.PaginationData;
/**
* @author chenshixian
**/
public interface ILoginLogService {
void addLoginLog(UserEntity userEntity, boolean success, String message);
void addLoginLog(UserEntity userEntity, boolean success, String message, String ipAddress, String userAgent);
void addLoginLog(String username, boolean success, String message, String ipAddress, String userAgent);
PaginationData<LoginLogDTO> getLoginLogList(LoginLogSearchParam param, int page, int size);
}
package com.dji.sample.manage.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.dji.sample.manage.model.dto.LoginLogDTO;
import com.dji.sample.manage.model.dto.UserDTO;
import com.dji.sample.manage.model.dto.UserListDTO;
import com.dji.sample.manage.model.dto.UserLoginDTO;
import com.dji.sample.manage.model.entity.UserEntity;
import com.dji.sample.manage.model.param.searchParam.LoginLogSearchParam;
import com.dji.sample.manage.model.param.searchParam.UserSearchParam;
import com.dji.sdk.common.HttpResultResponse;
import com.dji.sdk.common.PaginationData;
......@@ -22,6 +24,17 @@ public interface IUserService extends IService<UserEntity> {
HttpResultResponse getUserByUsername(String username, String workspaceId);
/**
* get user login log
*
* @param userId 用户ID
* @param roleType 角色类型
*
* @return 结果
*/
PaginationData<LoginLogDTO> getUserLoginLog(LoginLogSearchParam param, String userId, Integer roleType, int page, int pageSize);
/**
* Verify the username and password to log in.
* @param username
* @param password
......
package com.dji.sample.manage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dji.sample.common.util.DateUtil;
import com.dji.sample.manage.dao.ILoginLogMapper;
import com.dji.sample.manage.model.dto.LoginLogDTO;
import com.dji.sample.manage.model.entity.LoginLogEntity;
import com.dji.sample.manage.model.entity.UserEntity;
import com.dji.sample.manage.model.param.searchParam.LoginLogSearchParam;
import com.dji.sample.manage.service.ILoginLogService;
import com.dji.sdk.common.PaginationData;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author chenshixian
**/
@Service
public class LoginLogService implements ILoginLogService {
@Autowired
private ILoginLogMapper loginLogMapper;
/**
* 保存登录信息
* @param userEntity
* @param success
* @param message
*/
@Override
public void addLoginLog(UserEntity userEntity, boolean success, String message) {
addLoginLog(userEntity, success, message, null, null);
}
/**
* 保存登录信息(包含IP和User Agent)
*
* @param userEntity
* @param success
* @param message
* @param ipAddress
* @param userAgent
*/
@Override
public void addLoginLog(UserEntity userEntity, boolean success, String message, String ipAddress, String userAgent) {
LoginLogEntity loginLogEntity = new LoginLogEntity();
loginLogEntity.setUserId(userEntity.getUserId());
loginLogEntity.setUsername(userEntity.getUsername());
loginLogEntity.setSuccess(success);
loginLogEntity.setMessage(message);
loginLogEntity.setIpAddress(ipAddress);
loginLogEntity.setUserAgent(userAgent);
loginLogEntity.setCreateTime(System.currentTimeMillis());
loginLogMapper.insert(loginLogEntity);
}
/**
* 保存登录信息(仅用户名,用于登录失败场景)
*
* @param username
* @param success
* @param message
* @param ipAddress
* @param userAgent
*/
@Override
public void addLoginLog(String username, boolean success, String message, String ipAddress, String userAgent) {
LoginLogEntity loginLogEntity = new LoginLogEntity();
loginLogEntity.setUserId("");
loginLogEntity.setUsername(username);
loginLogEntity.setSuccess(success);
loginLogEntity.setMessage(message);
loginLogEntity.setIpAddress(ipAddress);
loginLogEntity.setUserAgent(userAgent);
loginLogEntity.setCreateTime(System.currentTimeMillis());
loginLogMapper.insert(loginLogEntity);
}
private LoginLogDTO entity2DTO(LoginLogEntity entity) {
LoginLogDTO.LoginLogDTOBuilder builder = LoginLogDTO.builder();
if (entity != null) {
builder.userId(entity.getUserId())
.id(entity.getId())
.userId(entity.getUserId())
.username(entity.getUsername())
.success(entity.isSuccess())
.message(entity.getMessage())
.ipAddress(entity.getIpAddress())
.userAgent(entity.getUserAgent())
.createTime(entity.getCreateTime());
}
return builder.build();
}
@Override
public PaginationData<LoginLogDTO> getLoginLogList(LoginLogSearchParam param, int page, int pageSize) {
LambdaQueryWrapper<LoginLogEntity> queryWrapper = new LambdaQueryWrapper<>();
if (param != null) {
if (StringUtils.isNotBlank(param.getUserId())) {
queryWrapper.eq(LoginLogEntity::getUserId, param.getUserId());
}
if (StringUtils.isNotBlank(param.getStartDate())) {
String startDate = param.getStartDate();
startDate += " 00:00:00";
queryWrapper.ge(LoginLogEntity::getCreateTime, DateUtil.getTimeStampFromDateStr(startDate));
}
if (StringUtils.isNotBlank(param.getEndDate())) {
String startDate = param.getStartDate();
startDate += " 23:59:59";
queryWrapper.lt(LoginLogEntity::getCreateTime, DateUtil.getTimeStampFromDateStrNext(startDate));
}
}
queryWrapper.orderByDesc(LoginLogEntity::getCreateTime);
Page<LoginLogEntity> userEntityPage = loginLogMapper.selectPage(new Page<>(page, pageSize), queryWrapper);
List<LoginLogDTO> usersList = userEntityPage.getRecords()
.stream()
.map(this::entity2DTO)
.collect(Collectors.toList());
return PaginationData.of(usersList, userEntityPage.getTotal(), page, pageSize);
}
}
......@@ -14,6 +14,8 @@ import com.dji.sample.common.util.SecurityUtils;
import com.dji.sample.component.mqtt.config.MqttPropertyConfiguration;
import com.dji.sample.component.oss.model.OssConfiguration;
import com.dji.sample.component.oss.service.impl.OssServiceContext;
import com.dji.sample.component.redis.RedisConst;
import com.dji.sample.component.redis.RedisOpsUtils;
import com.dji.sample.manage.dao.IUserMapper;
import com.dji.sample.manage.model.dto.*;
import com.dji.sample.manage.model.entity.OrgEntity;
......@@ -22,11 +24,9 @@ import com.dji.sample.manage.model.entity.UserOrgEntity;
import com.dji.sample.manage.model.entity.WorkspaceEntity;
import com.dji.sample.manage.model.enums.RoleTypeEnum;
import com.dji.sample.manage.model.enums.UserTypeEnum;
import com.dji.sample.manage.model.param.searchParam.LoginLogSearchParam;
import com.dji.sample.manage.model.param.searchParam.UserSearchParam;
import com.dji.sample.manage.service.IOrgService;
import com.dji.sample.manage.service.IUserOrgService;
import com.dji.sample.manage.service.IUserService;
import com.dji.sample.manage.service.IWorkspaceService;
import com.dji.sample.manage.service.*;
import com.dji.sdk.common.HttpResultResponse;
import com.dji.sdk.common.Pagination;
import com.dji.sdk.common.PaginationData;
......@@ -61,6 +61,10 @@ public class UserServiceImpl extends ServiceImpl<IUserMapper, UserEntity> implem
@Autowired
private IOrgService orgService;
@Autowired
private ILoginLogService loginLogService;
@Autowired
private IUserOrgService userOrgService;
......@@ -70,6 +74,45 @@ public class UserServiceImpl extends ServiceImpl<IUserMapper, UserEntity> implem
@Autowired
private OssServiceContext ossService;
/**
* 登录尝试次数
*/
public static final String LOGIN_TRY_COUNT = "LOGIN_TRY_COUNT";
private final static String LOGIN_USER_NOT_PASS = "username or password invalid";
private final static String LOGIN_USER_NAME_NOT_EXIST = "user name does not exist";
private final static String ACCOUNT_TYPE_NOT_MATCH = "Account type does not match";
private final static String INVALID_PASSWORD = "Invalid password";
private final static String INVALID_WORKSPACE = "Invalid workspace";
/**
* 最大尝试登录次数
*/
private final static Integer LOGIN_TRY_COUNT_MAX = 5;
/**
* 最大尝试登录次数
* 第二阶段
*/
private final static Integer LOGIN_TRY_COUNT_MAX_SECOND = 10;
/**
* 登录尝试的锁
*/
private final static String LOGIN_TRY_LOCK = "LOGIN_TRY_LOCK";
private final static String LOGIN_TRY_COUNT_MAX_EXCEEDED = "login try count max exceeded, user locked";
private final static long HOUR_SECONDS = 60 * 60;
private final static long DATE_SECONDS = 24 * 60 * 60;
@Override
public HttpResultResponse getUserByUsername(String username, String workspaceId) {
......@@ -86,6 +129,109 @@ public class UserServiceImpl extends ServiceImpl<IUserMapper, UserEntity> implem
return HttpResultResponse.success(user);
}
@Override
public PaginationData<LoginLogDTO> getUserLoginLog(LoginLogSearchParam param, String userId, Integer roleType, int page, int pageSize) {
if (param == null) {
param = new LoginLogSearchParam();
}
if (roleType == null || roleType != RoleTypeEnum.ADMIN.getVal()) {
param.setUserId(userId);
}
return loginLogService.getLoginLogList(param, page, pageSize);
}
/**
* 更新用户尝试登录次数
*
* @param user 用户
* @param loginTryCount 登录尝试次数
*/
private void updateUserLoginTryCount(UserEntity user, Integer loginTryCount) {
LambdaUpdateWrapper<UserEntity> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(UserEntity::getLoginTryCount, loginTryCount);
updateWrapper.eq(UserEntity::getId, user.getId());
mapper.update(null, updateWrapper);
}
/**
* 解锁用户
*
* @param userEntity 实体
*/
public void unlockUser(UserEntity userEntity) {
String username = userEntity.getUsername();
updateUserLoginTryCount(userEntity, 0);
RedisOpsUtils.del(LOGIN_TRY_COUNT + RedisConst.DELIMITER + username);
RedisOpsUtils.del(LOGIN_TRY_LOCK + RedisConst.DELIMITER + username);
}
/**
* 处理登录密码错误的问题
*/
private void handleLoginPasswordFail(String ipAddress, String userAgent, UserEntity userEntity) {
Integer loginTryCount = userEntity.getLoginTryCount();
if (loginTryCount == null) {
loginTryCount = 0;
}
loginTryCount++;
// 更新用户尝试登录次数
updateUserLoginTryCount(userEntity, loginTryCount);
// 把登录尝试次数放在Redis中
addLoginTryCountToRedis(userEntity.getUsername());
// 增加登录日志
loginLogService.addLoginLog(userEntity, false, INVALID_PASSWORD, ipAddress, userAgent);
}
private void addLoginTryCountToRedis(String username) {
String redisKey = LOGIN_TRY_COUNT + RedisConst.DELIMITER + username;
Integer loginTryCount = RedisOpsUtils.getInt(redisKey);
if (loginTryCount == null) {
loginTryCount = 0;
}
loginTryCount++;
// 把尝试次数加入到redis中
RedisOpsUtils.setWithExpire(redisKey, loginTryCount, DATE_SECONDS);
// 超过尝试登录次数的话,用户锁定
if (loginTryCount.equals(LOGIN_TRY_COUNT_MAX)) {
RedisOpsUtils.setWithExpire(LOGIN_TRY_LOCK + RedisConst.DELIMITER + username, System.currentTimeMillis(), HOUR_SECONDS);
}
// 超过尝试登录次数的第二阶段的话,用户锁定更长的时间
if (loginTryCount >= LOGIN_TRY_COUNT_MAX_SECOND) {
RedisOpsUtils.setWithExpire(LOGIN_TRY_LOCK + RedisConst.DELIMITER + username, System.currentTimeMillis(), DATE_SECONDS);
}
}
/**
* 检查当前的IP,尝试登录的次数是否超过
*
* @param username 用户名
* @return 结果
*/
private boolean checkLoginTryCountLock(String username) {
String redisKey = LOGIN_TRY_LOCK + RedisConst.DELIMITER + username;
return !RedisOpsUtils.checkExist(redisKey);
}
/**
* Verify the username and password to log in.
* @param loginDTO
......@@ -94,6 +240,9 @@ public class UserServiceImpl extends ServiceImpl<IUserMapper, UserEntity> implem
@Override
public
HttpResultResponse userLogin(UserLoginDTO loginDTO) {
String username = loginDTO.getUsername();
Integer flag = loginDTO.getFlag();
String password = loginDTO.getPassword();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment