feat: 增加开发模式 URL 免登录
- 新增 dev-only 且仅限本机访问的 admin 免登入口 - 管理端支持通过 ?devLogin=admin 自动换取登录态并清理 URL 参数 - 删除未受保护的临时 token 接口并补充关键单测
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package tech.easyflow.auth.config;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
@Component
|
||||
public class DevLoginGuard {
|
||||
|
||||
private final LoginProperties properties;
|
||||
|
||||
public DevLoginGuard(LoginProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public void checkAccess(HttpServletRequest request, String account) {
|
||||
if (!isAllowedAccount(account)) {
|
||||
throw new BusinessException("仅允许使用 admin 账号进行开发免登");
|
||||
}
|
||||
if (properties.getDevBypass().isLoopbackOnly() && !isLoopbackRequest(request)) {
|
||||
throw new BusinessException("开发免登仅允许本机访问");
|
||||
}
|
||||
}
|
||||
|
||||
boolean isAllowedAccount(String account) {
|
||||
return StringUtils.hasText(account)
|
||||
&& account.equals(properties.getDevBypass().getAccount());
|
||||
}
|
||||
|
||||
boolean isLoopbackRequest(HttpServletRequest request) {
|
||||
return request != null && isLoopbackAddress(request.getRemoteAddr());
|
||||
}
|
||||
|
||||
boolean isLoopbackAddress(String remoteAddr) {
|
||||
if (!StringUtils.hasText(remoteAddr)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return InetAddress.getByName(remoteAddr).isLoopbackAddress();
|
||||
} catch (UnknownHostException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
public class LoginProperties {
|
||||
|
||||
private String[] excludes;
|
||||
private DevBypassProperties devBypass = new DevBypassProperties();
|
||||
|
||||
public String[] getExcludes() {
|
||||
return excludes;
|
||||
@@ -20,4 +21,43 @@ public class LoginProperties {
|
||||
public void setExcludes(String[] excludes) {
|
||||
this.excludes = excludes;
|
||||
}
|
||||
|
||||
public DevBypassProperties getDevBypass() {
|
||||
return devBypass;
|
||||
}
|
||||
|
||||
public void setDevBypass(DevBypassProperties devBypass) {
|
||||
this.devBypass = devBypass;
|
||||
}
|
||||
|
||||
public static class DevBypassProperties {
|
||||
|
||||
private String account = "admin";
|
||||
private boolean enabled = false;
|
||||
private boolean loopbackOnly = true;
|
||||
|
||||
public String getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public void setAccount(String account) {
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isLoopbackOnly() {
|
||||
return loopbackOnly;
|
||||
}
|
||||
|
||||
public void setLoopbackOnly(boolean loopbackOnly) {
|
||||
this.loopbackOnly = loopbackOnly;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,4 +8,9 @@ public interface AuthService {
|
||||
* 登录
|
||||
*/
|
||||
LoginVO login(LoginDTO loginDTO);
|
||||
|
||||
/**
|
||||
* 开发模式免登录
|
||||
*/
|
||||
LoginVO devLogin(String account);
|
||||
}
|
||||
|
||||
@@ -38,35 +38,29 @@ public class AuthServiceImpl implements AuthService, StpInterface {
|
||||
|
||||
@Override
|
||||
public LoginVO login(LoginDTO loginDTO) {
|
||||
LoginVO res = new LoginVO();
|
||||
try {
|
||||
TenantManager.ignoreTenantCondition();
|
||||
String pwd = loginDTO.getPassword();
|
||||
QueryWrapper w = QueryWrapper.create();
|
||||
w.eq(SysAccount::getLoginName, loginDTO.getAccount());
|
||||
SysAccount record = sysAccountService.getOne(w);
|
||||
if (record == null) {
|
||||
throw new BusinessException("用户名/密码错误");
|
||||
}
|
||||
if (EnumDataStatus.UNAVAILABLE.getCode().equals(record.getStatus())) {
|
||||
throw new BusinessException("账号未启用,请联系管理员");
|
||||
}
|
||||
SysAccount record = getAvailableAccount(loginDTO.getAccount(), "用户名/密码错误");
|
||||
String pwdDb = record.getPassword();
|
||||
if (!BCrypt.checkpw(pwd, pwdDb)) {
|
||||
throw new BusinessException("用户名/密码错误");
|
||||
}
|
||||
StpUtil.login(record.getId());
|
||||
LoginAccount loginAccount = new LoginAccount();
|
||||
BeanUtil.copyProperties(record, loginAccount);
|
||||
StpUtil.getSession().set(Constants.LOGIN_USER_KEY, loginAccount);
|
||||
String tokenValue = StpUtil.getTokenValue();
|
||||
res.setToken(tokenValue);
|
||||
res.setNickname(record.getNickname());
|
||||
res.setAvatar(record.getAvatar());
|
||||
return createLoginVO(record);
|
||||
} finally {
|
||||
TenantManager.restoreTenantCondition();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoginVO devLogin(String account) {
|
||||
try {
|
||||
TenantManager.ignoreTenantCondition();
|
||||
SysAccount record = getAvailableAccount(account, "开发免登账号不存在");
|
||||
return createLoginVO(record);
|
||||
} finally {
|
||||
TenantManager.restoreTenantCondition();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,4 +77,30 @@ public class AuthServiceImpl implements AuthService, StpInterface {
|
||||
List<SysRole> roles = sysRoleService.getRolesByAccountId(BigInteger.valueOf(Long.parseLong(loginId.toString())));
|
||||
return roles.stream().map(SysRole::getRoleKey).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private LoginVO createLoginVO(SysAccount record) {
|
||||
StpUtil.login(record.getId());
|
||||
LoginAccount loginAccount = new LoginAccount();
|
||||
BeanUtil.copyProperties(record, loginAccount);
|
||||
StpUtil.getSession().set(Constants.LOGIN_USER_KEY, loginAccount);
|
||||
|
||||
LoginVO res = new LoginVO();
|
||||
res.setToken(StpUtil.getTokenValue());
|
||||
res.setNickname(record.getNickname());
|
||||
res.setAvatar(record.getAvatar());
|
||||
return res;
|
||||
}
|
||||
|
||||
private SysAccount getAvailableAccount(String account, String accountNotFoundMessage) {
|
||||
QueryWrapper w = QueryWrapper.create();
|
||||
w.eq(SysAccount::getLoginName, account);
|
||||
SysAccount record = sysAccountService.getOne(w);
|
||||
if (record == null) {
|
||||
throw new BusinessException(accountNotFoundMessage);
|
||||
}
|
||||
if (EnumDataStatus.UNAVAILABLE.getCode().equals(record.getStatus())) {
|
||||
throw new BusinessException("账号未启用,请联系管理员");
|
||||
}
|
||||
return record;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package tech.easyflow.auth.config;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class DevLoginGuardTest {
|
||||
|
||||
@Test
|
||||
public void shouldAcceptConfiguredAdminAccount() {
|
||||
DevLoginGuard guard = new DevLoginGuard(createProperties());
|
||||
Assert.assertTrue(guard.isAllowedAccount("admin"));
|
||||
Assert.assertFalse(guard.isAllowedAccount("guest"));
|
||||
Assert.assertFalse(guard.isAllowedAccount(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRecognizeLoopbackAddresses() {
|
||||
DevLoginGuard guard = new DevLoginGuard(createProperties());
|
||||
Assert.assertTrue(guard.isLoopbackAddress("127.0.0.1"));
|
||||
Assert.assertTrue(guard.isLoopbackAddress("::1"));
|
||||
Assert.assertFalse(guard.isLoopbackAddress("192.168.1.10"));
|
||||
Assert.assertFalse(guard.isLoopbackAddress("not-an-ip"));
|
||||
}
|
||||
|
||||
private LoginProperties createProperties() {
|
||||
LoginProperties properties = new LoginProperties();
|
||||
LoginProperties.DevBypassProperties devBypass = new LoginProperties.DevBypassProperties();
|
||||
devBypass.setEnabled(true);
|
||||
devBypass.setAccount("admin");
|
||||
devBypass.setLoopbackOnly(true);
|
||||
properties.setDevBypass(devBypass);
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user