mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 01:50:30 +08:00
feat(m11): add login failure lockout after 5 failed attempts in 15 min
This commit is contained in:
+38
-1
@@ -1,7 +1,11 @@
|
||||
package cn.craftlabs.platform.api.auth;
|
||||
|
||||
import cn.craftlabs.platform.api.persistence.auth.PlatformLoginAttempt;
|
||||
import cn.craftlabs.platform.api.persistence.auth.PlatformLoginAttemptMapper;
|
||||
import cn.craftlabs.platform.api.security.JwtService;
|
||||
import cn.craftlabs.platform.api.security.PlatformRoles;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -9,6 +13,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -20,15 +25,39 @@ import java.util.Map;
|
||||
public class AuthController {
|
||||
|
||||
private final JwtService jwtService;
|
||||
private final PlatformLoginAttemptMapper loginAttemptMapper;
|
||||
private final HttpServletRequest request;
|
||||
|
||||
public AuthController(JwtService jwtService) {
|
||||
public AuthController(JwtService jwtService, PlatformLoginAttemptMapper loginAttemptMapper, HttpServletRequest request) {
|
||||
this.jwtService = jwtService;
|
||||
this.loginAttemptMapper = loginAttemptMapper;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public Map<String, Object> login(@RequestBody Map<String, String> body) {
|
||||
String user = body.getOrDefault("username", "");
|
||||
String pass = body.getOrDefault("password", "");
|
||||
|
||||
LambdaQueryWrapper<PlatformLoginAttempt> recentQuery = new LambdaQueryWrapper<>();
|
||||
recentQuery.eq(PlatformLoginAttempt::getUsername, user)
|
||||
.eq(PlatformLoginAttempt::getSuccess, false)
|
||||
.ge(PlatformLoginAttempt::getAttemptedAt, OffsetDateTime.now().minusMinutes(15));
|
||||
long recentFailures = loginAttemptMapper.selectCount(recentQuery);
|
||||
|
||||
if (recentFailures >= 5) {
|
||||
PlatformLoginAttempt attempt = new PlatformLoginAttempt();
|
||||
attempt.setUsername(user);
|
||||
attempt.setSuccess(false);
|
||||
attempt.setIpAddress(request.getRemoteAddr());
|
||||
attempt.setAttemptedAt(OffsetDateTime.now());
|
||||
loginAttemptMapper.insert(attempt);
|
||||
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.TOO_MANY_REQUESTS,
|
||||
"账户已临时锁定,请 15 分钟后重试");
|
||||
}
|
||||
|
||||
if ("admin".equals(user) && "admin".equals(pass)) {
|
||||
String token =
|
||||
jwtService.createToken(user, "管理员", List.of(PlatformRoles.SYS_ADMIN));
|
||||
@@ -67,6 +96,14 @@ public class AuthController {
|
||||
"displayName",
|
||||
"运营账号");
|
||||
}
|
||||
|
||||
PlatformLoginAttempt attempt = new PlatformLoginAttempt();
|
||||
attempt.setUsername(user);
|
||||
attempt.setSuccess(false);
|
||||
attempt.setIpAddress(request.getRemoteAddr());
|
||||
attempt.setAttemptedAt(OffsetDateTime.now());
|
||||
loginAttemptMapper.insert(attempt);
|
||||
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "invalid credentials");
|
||||
}
|
||||
}
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
package cn.craftlabs.platform.api.persistence.auth;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@TableName("platform_login_attempt")
|
||||
public class PlatformLoginAttempt {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private String username;
|
||||
|
||||
private Boolean success;
|
||||
|
||||
@TableField("ip_address")
|
||||
private String ipAddress;
|
||||
|
||||
@TableField("attempted_at")
|
||||
private OffsetDateTime attemptedAt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public Boolean getSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(Boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public String getIpAddress() {
|
||||
return ipAddress;
|
||||
}
|
||||
|
||||
public void setIpAddress(String ipAddress) {
|
||||
this.ipAddress = ipAddress;
|
||||
}
|
||||
|
||||
public OffsetDateTime getAttemptedAt() {
|
||||
return attemptedAt;
|
||||
}
|
||||
|
||||
public void setAttemptedAt(OffsetDateTime attemptedAt) {
|
||||
this.attemptedAt = attemptedAt;
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package cn.craftlabs.platform.api.persistence.auth;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PlatformLoginAttemptMapper extends BaseMapper<PlatformLoginAttempt> {}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
CREATE TABLE platform_login_attempt (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
username VARCHAR(256) NOT NULL,
|
||||
success BOOLEAN NOT NULL,
|
||||
ip_address VARCHAR(64),
|
||||
attempted_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX idx_login_attempt_username ON platform_login_attempt(username);
|
||||
CREATE INDEX idx_login_attempt_time ON platform_login_attempt(attempted_at);
|
||||
Reference in New Issue
Block a user