feat: NewSystemInitializer and token verify

This commit is contained in:
tjq 2024-02-16 12:38:42 +08:00
parent 9419340829
commit 686189e6ca
27 changed files with 822 additions and 207 deletions

View File

@ -1,5 +1,6 @@
package tech.powerjob.common.serialize;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
@ -32,6 +33,10 @@ public class JsonUtils {
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.build();
static {
JSON_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
private static final TypeReference<Map<String, Object>> MAP_TYPE_REFERENCE = new TypeReference<Map<String, Object>> () {};
private JsonUtils(){
@ -39,6 +44,9 @@ public class JsonUtils {
}
public static String toJSONString(Object obj) {
if (obj == null) {
return null;
}
if (obj instanceof String) {
return (String) obj;
}

View File

@ -26,6 +26,13 @@ public class AuthConstants {
*/
public static final String ACCOUNT_LARK = "LARK";
public static final String PARAM_KEY_USERNAME = "username";
public static final String PARAM_KEY_PASSWORD = "password";
/**
* 前端参数-密码加密类型官方版本出于成本未进行前后端传输的对称加密接入方有需求可自行实现此处定义加密协议字段
*/
public static final String PARAM_KEY_ENCRYPTION = "encryption";
/* ********** 账号体系 ********** */
/**
@ -40,4 +47,6 @@ public class AuthConstants {
public static final String FE_REDIRECT_KEY = "FE-REDIRECT:";
public static final String TIPS_NO_PERMISSION_TO_SEE = "NO_PERMISSION_TO_SEE";
public static final Long GLOBAL_ADMIN_TARGET_ID = 1L;
}

View File

@ -15,6 +15,7 @@ public enum AuthErrorCode {
USER_NOT_LOGIN("-100", "UserNotLoggedIn"),
USER_NOT_EXIST("-101", "UserNotExist"),
USER_AUTH_FAILED("-102", "UserAuthFailed"),
NO_PERMISSION("-200", "NoPermission"),
@ -24,7 +25,9 @@ public enum AuthErrorCode {
*/
INVALID_REQUEST("-300", "INVALID_REQUEST"),
INCORRECT_PASSWORD("-400", "INCORRECT_PASSWORD")
INCORRECT_PASSWORD("-400", "INCORRECT_PASSWORD"),
INVALID_TOKEN("-401", "INVALID_TOKEN"),
;

View File

@ -30,4 +30,13 @@ public interface ThirdPartyLoginService {
*/
ThirdPartyUser login(ThirdPartyLoginRequest loginRequest);
/**
* JWT 登录的回调校验
* @param username 用户名称
* @param tokenLoginVerifyInfo 二次校验信息
* @return 是否通过
*/
default boolean tokenLoginVerify(String username, TokenLoginVerifyInfo tokenLoginVerifyInfo) {
return true;
}
}

View File

@ -17,6 +17,11 @@ public class ThirdPartyUser {
* 用户的唯一标识用于关联到 PowerJob username
*/
private String username;
/**
* JWT 登录的二次校验配置
* 可空空则代表放弃二次校验会出现第三方登录改了密码当 PowerJob JWT 登录依然可用的情况
*/
private TokenLoginVerifyInfo tokenLoginVerifyInfo;
/* ******** 以下全部选填即可,只是方便数据同步,后续都可以去 PowerJob 控制台更改 ******** */
/**

View File

@ -0,0 +1,31 @@
package tech.powerjob.server.auth.login;
import lombok.Data;
import java.io.Serializable;
/**
* JWT 登录时的校验信息
*
* @author tjq
* @since 2024/2/16
*/
@Data
public class TokenLoginVerifyInfo implements Serializable {
/**
* 加密 token 部分比如密码的 md5会直接写入 JWT 下发给前端
* 如果需要使用 JWT 二次校验则该参数必须存在
*/
private String encryptedToken;
/**
* 补充信息用于二次校验
*/
private String additionalInfo;
/**
* 依然是预留字段第三方实现自用即可
*/
private String extra;
}

View File

@ -8,10 +8,7 @@ import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.server.auth.common.AuthConstants;
import tech.powerjob.server.auth.common.AuthErrorCode;
import tech.powerjob.server.auth.common.PowerJobAuthException;
import tech.powerjob.server.auth.login.LoginTypeInfo;
import tech.powerjob.server.auth.login.ThirdPartyLoginRequest;
import tech.powerjob.server.auth.login.ThirdPartyLoginService;
import tech.powerjob.server.auth.login.ThirdPartyUser;
import tech.powerjob.server.auth.login.*;
import tech.powerjob.server.common.Loggers;
import tech.powerjob.server.common.utils.DigestUtils;
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
@ -35,10 +32,6 @@ public class PowerJobThirdPartyLoginService implements ThirdPartyLoginService {
@Resource
private PwjbUserInfoRepository pwjbUserInfoRepository;
private static final String KEY_USERNAME = "username";
private static final String KEY_PASSWORD = "password";
private static final String KEY_ENCRYPTION = "encryption";
@Override
public LoginTypeInfo loginType() {
@ -63,9 +56,11 @@ public class PowerJobThirdPartyLoginService implements ThirdPartyLoginService {
Map<String, Object> loginInfoMap = JsonUtils.parseMap(loginInfo);
final String username = MapUtils.getString(loginInfoMap, KEY_USERNAME);
final String password = MapUtils.getString(loginInfoMap, KEY_PASSWORD);
final String encryption = MapUtils.getString(loginInfoMap, KEY_ENCRYPTION);
final String username = MapUtils.getString(loginInfoMap, AuthConstants.PARAM_KEY_USERNAME);
final String password = MapUtils.getString(loginInfoMap, AuthConstants.PARAM_KEY_PASSWORD);
final String encryption = MapUtils.getString(loginInfoMap, AuthConstants.PARAM_KEY_ENCRYPTION);
Loggers.WEB.debug("[PowerJobLoginService] username: {}, password: {}, encryption: {}", username, password, encryption);
if (StringUtils.isAnyEmpty(username, password)) {
Loggers.WEB.debug("[PowerJobLoginService] username or password is empty, login failed!");
@ -96,10 +91,31 @@ public class PowerJobThirdPartyLoginService implements ThirdPartyLoginService {
}
}
// 下发加密的密码作为 JWT 的一部分方便处理改密码后失效的场景
TokenLoginVerifyInfo tokenLoginVerifyInfo = new TokenLoginVerifyInfo();
tokenLoginVerifyInfo.setEncryptedToken(dbUser.getPassword());
bizUser.setTokenLoginVerifyInfo(tokenLoginVerifyInfo);
return bizUser;
}
Loggers.WEB.debug("[PowerJobLoginService] user[{}]'s password is incorrect, login failed!", username);
throw new PowerJobException("password is incorrect");
}
@Override
public boolean tokenLoginVerify(String username, TokenLoginVerifyInfo tokenLoginVerifyInfo) {
if (tokenLoginVerifyInfo == null) {
return false;
}
final Optional<PwjbUserInfoDO> userInfoOpt = pwjbUserInfoRepository.findByUsername(username);
if (userInfoOpt.isPresent()) {
String dbPassword = userInfoOpt.get().getPassword();
return StringUtils.equals(dbPassword, tokenLoginVerifyInfo.getEncryptedToken());
}
return false;
}
}

View File

@ -25,5 +25,8 @@ public class LoginRequest {
*/
private String originParams;
/**
* http原始请求第三方回调参数传递类型无法枚举直接传递 HttpServletRequest 满足扩展性要求
*/
private transient HttpServletRequest httpServletRequest;
}

View File

@ -2,11 +2,15 @@ package tech.powerjob.server.auth.service.login.impl;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.server.auth.LoginUserHolder;
import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.common.AuthConstants;
@ -14,10 +18,7 @@ import tech.powerjob.server.auth.common.AuthErrorCode;
import tech.powerjob.server.auth.common.PowerJobAuthException;
import tech.powerjob.server.auth.common.utils.HttpServletUtils;
import tech.powerjob.server.auth.jwt.JwtService;
import tech.powerjob.server.auth.login.LoginTypeInfo;
import tech.powerjob.server.auth.login.ThirdPartyLoginRequest;
import tech.powerjob.server.auth.login.ThirdPartyLoginService;
import tech.powerjob.server.auth.login.ThirdPartyUser;
import tech.powerjob.server.auth.login.*;
import tech.powerjob.server.auth.service.login.LoginRequest;
import tech.powerjob.server.auth.service.login.PowerJobLoginService;
import tech.powerjob.server.common.Loggers;
@ -26,6 +27,8 @@ import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -41,15 +44,10 @@ import java.util.stream.Collectors;
@Service
public class PowerJobLoginServiceImpl implements PowerJobLoginService {
private final JwtService jwtService;
private final UserInfoRepository userInfoRepository;
private final Map<String, ThirdPartyLoginService> code2ThirdPartyLoginService;
private static final String KEY_USERNAME = "userName";
@Autowired
public PowerJobLoginServiceImpl(JwtService jwtService, UserInfoRepository userInfoRepository, List<ThirdPartyLoginService> thirdPartyLoginServices) {
@ -96,6 +94,8 @@ public class PowerJobLoginServiceImpl implements PowerJobLoginService {
newUser.setAccountType(loginType);
newUser.setOriginUsername(bizUser.getUsername());
newUser.setTokenLoginVerifyInfo(JsonUtils.toJSONString(bizUser.getTokenLoginVerifyInfo()));
// 同步素材
newUser.setEmail(bizUser.getEmail());
newUser.setPhone(bizUser.getPhone());
@ -107,6 +107,15 @@ public class PowerJobLoginServiceImpl implements PowerJobLoginService {
userInfoRepository.saveAndFlush(newUser);
powerJobUserOpt = userInfoRepository.findByUsername(dbUserName);
} else {
// 更新二次校验的 TOKEN 信息
UserInfoDO dbUserInfoDO = powerJobUserOpt.get();
dbUserInfoDO.setTokenLoginVerifyInfo(JsonUtils.toJSONString(bizUser.getTokenLoginVerifyInfo()));
dbUserInfoDO.setGmtModified(new Date());
userInfoRepository.saveAndFlush(dbUserInfoDO);
}
PowerJobUser ret = new PowerJobUser();
@ -118,23 +127,52 @@ public class PowerJobLoginServiceImpl implements PowerJobLoginService {
ret.setUsername(dbUserName);
}
fillJwt(ret);
fillJwt(ret, Optional.ofNullable(bizUser.getTokenLoginVerifyInfo()).map(TokenLoginVerifyInfo::getEncryptedToken).orElse(null));
return ret;
}
@Override
public Optional<PowerJobUser> ifLogin(HttpServletRequest httpServletRequest) {
final Optional<String> userNameOpt = parseUserName(httpServletRequest);
return userNameOpt.flatMap(uname -> userInfoRepository.findByUsername(uname).map(userInfoDO -> {
PowerJobUser powerJobUser = new PowerJobUser();
BeanUtils.copyProperties(userInfoDO, powerJobUser);
final Optional<JwtBody> jwtBodyOpt = parseJwt(httpServletRequest);
if (!jwtBodyOpt.isPresent()) {
return Optional.empty();
}
// 兼容某些直接通过 ifLogin 判断登录的场景
LoginUserHolder.set(powerJobUser);
JwtBody jwtBody = jwtBodyOpt.get();
return powerJobUser;
}));
Optional<UserInfoDO> dbUserInfoOpt = userInfoRepository.findByUsername(jwtBody.getUsername());
if (!dbUserInfoOpt.isPresent()) {
throw new PowerJobAuthException(AuthErrorCode.USER_NOT_EXIST);
}
UserInfoDO dbUser = dbUserInfoOpt.get();
PowerJobUser powerJobUser = new PowerJobUser();
String tokenLoginVerifyInfoStr = dbUser.getTokenLoginVerifyInfo();
TokenLoginVerifyInfo tokenLoginVerifyInfo = Optional.ofNullable(tokenLoginVerifyInfoStr).map(x -> JsonUtils.parseObjectIgnoreException(x, TokenLoginVerifyInfo.class)).orElse(new TokenLoginVerifyInfo());
// DB 中的 encryptedToken 存在代表需要二次校验
if (StringUtils.isNotEmpty(tokenLoginVerifyInfo.getEncryptedToken())) {
if (!StringUtils.equals(jwtBody.getEncryptedToken(), tokenLoginVerifyInfo.getEncryptedToken())) {
throw new PowerJobAuthException(AuthErrorCode.INVALID_TOKEN);
}
ThirdPartyLoginService thirdPartyLoginService = code2ThirdPartyLoginService.get(dbUser.getAccountType());
boolean tokenLoginVerifyOk = thirdPartyLoginService.tokenLoginVerify(dbUser.getOriginUsername(), tokenLoginVerifyInfo);
if (!tokenLoginVerifyOk) {
throw new PowerJobAuthException(AuthErrorCode.USER_AUTH_FAILED);
}
}
BeanUtils.copyProperties(dbUser, powerJobUser);
// 兼容某些直接通过 ifLogin 判断登录的场景
LoginUserHolder.set(powerJobUser);
return Optional.of(powerJobUser);
}
private ThirdPartyLoginService fetchBizLoginService(String loginType) {
@ -145,16 +183,22 @@ public class PowerJobLoginServiceImpl implements PowerJobLoginService {
return loginService;
}
private void fillJwt(PowerJobUser powerJobUser) {
Map<String, Object> jwtMap = Maps.newHashMap();
private void fillJwt(PowerJobUser powerJobUser, String encryptedToken) {
// 不能下发 userId容易被轮询爆破
jwtMap.put(KEY_USERNAME, powerJobUser.getUsername());
JwtBody jwtBody = new JwtBody();
jwtBody.setUsername(powerJobUser.getUsername());
if (StringUtils.isNotEmpty(encryptedToken)) {
jwtBody.setEncryptedToken(encryptedToken);
}
Map<String, Object> jwtMap = JsonUtils.parseMap(JsonUtils.toJSONString(jwtBody));
powerJobUser.setJwtToken(jwtService.build(jwtMap, null));
}
private Optional<String> parseUserName(HttpServletRequest httpServletRequest) {
@SneakyThrows
private Optional<JwtBody> parseJwt(HttpServletRequest httpServletRequest) {
// headercookie 都能获取
String jwtStr = HttpServletUtils.fetchFromHeader(AuthConstants.JWT_NAME, httpServletRequest);
@ -169,12 +213,19 @@ public class PowerJobLoginServiceImpl implements PowerJobLoginService {
return Optional.empty();
}
final Map<String, Object> jwtBodyMap = jwtService.parse(jwtStr, null);
final Object userName = jwtBodyMap.get(KEY_USERNAME);
if (userName == null) {
if (MapUtils.isEmpty(jwtBodyMap)) {
return Optional.empty();
}
return Optional.of(String.valueOf(userName));
return Optional.ofNullable(JsonUtils.parseObject(JsonUtils.toJSONString(jwtBodyMap), JwtBody.class));
}
@Data
static class JwtBody implements Serializable {
private String username;
private String encryptedToken;
}
}

View File

@ -27,23 +27,23 @@ public interface PowerJobPermissionService {
boolean hasPermission(Long userId, RoleScope roleScope, Long target, Permission permission);
/**
* 授予用户权限
* 授予用户角色
* @param roleScope 权限范围
* @param target 权限目标
* @param userId 用户ID
* @param role 角色
* @param extra 其他
*/
void grantPermission(RoleScope roleScope, Long target, Long userId, Role role, String extra);
void grantRole(RoleScope roleScope, Long target, Long userId, Role role, String extra);
/**
* 回收用户权限
* 回收用户角色
* @param roleScope 权限范围
* @param target 权限目标
* @param userId 用户ID
* @param role 角色
*/
void retrievePermission(RoleScope roleScope, Long target, Long userId, Role role);
void retrieveRole(RoleScope roleScope, Long target, Long userId, Role role);
/**
* 获取有相关权限的用户

View File

@ -85,7 +85,7 @@ public class PowerJobPermissionServiceImpl implements PowerJobPermissionService
}
@Override
public void grantPermission(RoleScope roleScope, Long target, Long userId, Role role, String extra) {
public void grantRole(RoleScope roleScope, Long target, Long userId, Role role, String extra) {
UserRoleDO userRoleDO = new UserRoleDO();
userRoleDO.setGmtCreate(new Date());
@ -102,7 +102,7 @@ public class PowerJobPermissionServiceImpl implements PowerJobPermissionService
}
@Override
public void retrievePermission(RoleScope roleScope, Long target, Long userId, Role role) {
public void retrieveRole(RoleScope roleScope, Long target, Long userId, Role role) {
List<UserRoleDO> originUserRole = userRoleRepository.findAllByScopeAndTargetAndRoleAndUserId(roleScope.getV(), target, role.getV(), userId);
log.info("[PowerJobPermissionService] [retrievePermission] origin rule: {}", originUserRole);
Optional.ofNullable(originUserRole).orElse(Collections.emptyList()).forEach(r -> {

View File

@ -0,0 +1,49 @@
package tech.powerjob.server.persistence.remote.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
import java.util.Date;
/**
* 杂项
* KKV 表存一些配置数据
*
* @author tjq
* @since 2024/2/15
*/
@Data
@Entity
@NoArgsConstructor
@Table(uniqueConstraints = {@UniqueConstraint(name = "uidx01_sundry", columnNames = {"pkey", "skey"})})
public class SundryDO {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
@GenericGenerator(name = "native", strategy = "native")
private Long id;
/**
* PKEY
*/
private String pkey;
/**
* SKEY
*/
private String skey;
/**
* 内容
*/
private String content;
/**
* 其他参数
*/
private String extra;
private Date gmtCreate;
private Date gmtModified;
}

View File

@ -54,6 +54,12 @@ public class UserInfoDO {
* webHook
*/
private String webHook;
/**
* JWT 登录的二次校验信息
*/
private String tokenLoginVerifyInfo;
/**
* 扩展字段 for 第三方
* PowerJob 内部不允许使用该字段

View File

@ -0,0 +1,20 @@
package tech.powerjob.server.persistence.remote.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import tech.powerjob.server.persistence.remote.model.SundryDO;
import java.util.List;
import java.util.Optional;
/**
* SundryRepository
*
* @author tjq
* @since 2024/2/15
*/
public interface SundryRepository extends JpaRepository<SundryDO, Long> {
List<SundryDO> findAllByPkey(String pkey);
Optional<SundryDO> findByPkeyAndSkey(String pkey, String skey);
}

View File

@ -69,7 +69,7 @@ public abstract class SaveGrantPermissionPlugin implements GrantPermissionPlugin
Map<String, Object> extra = Maps.newHashMap();
extra.put("source", "SaveGrantPermissionPlugin");
powerJobPermissionService.grantPermission(fetchRuleScope(), savedId, powerJobUser.getId(), Role.ADMIN, JsonUtils.toJSONString(extra));
powerJobPermissionService.grantRole(fetchRuleScope(), savedId, powerJobUser.getId(), Role.ADMIN, JsonUtils.toJSONString(extra));
}
protected abstract RoleScope fetchRuleScope();

View File

@ -91,14 +91,14 @@ public class WebAuthServiceImpl implements WebAuthService {
// originUids 不在 currentUids需要取消授权
allIds.removeAll(currentUids);
allIds.forEach(cancelPermissionUid -> {
powerJobPermissionService.retrievePermission(roleScope, target, cancelPermissionUid, role);
powerJobPermissionService.retrieveRole(roleScope, target, cancelPermissionUid, role);
log.info("[WebAuthService] [diffGrant] cancelPermission: roleScope={},target={},uid={},role={}", roleScope, target, cancelPermissionUid, role);
});
// currentUids 当不在 orignUids需要增加授权
allIds2.removeAll(originUids);
allIds2.forEach(addPermissionUid -> {
powerJobPermissionService.grantPermission(roleScope, target, addPermissionUid, role, extra);
powerJobPermissionService.grantRole(roleScope, target, addPermissionUid, role, extra);
log.info("[WebAuthService] [diffGrant] grantPermission: roleScope={},target={},uid={},role={},extra={}", roleScope, target, addPermissionUid, role, extra);
});
}

View File

@ -0,0 +1,102 @@
package tech.powerjob.server.initializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import tech.powerjob.common.utils.CommonUtils;
import tech.powerjob.server.extension.LockService;
import tech.powerjob.server.persistence.remote.model.SundryDO;
import tech.powerjob.server.persistence.remote.repository.SundryRepository;
import javax.annotation.Resource;
import java.util.Date;
import java.util.Optional;
import java.util.function.Consumer;
/**
* 新系统初始化器
*
* @author tjq
* @since 2023/9/5
*/
@Slf4j
@Component
public class NewSystemInitializer implements CommandLineRunner {
private static final String LOCK_PREFIX = "sys_init_lock_";
private static final int MAX_LOCK_TIME = 5000;
@Resource
private LockService lockService;
@Resource
private SundryRepository sundryRepository;
@Resource
private SystemInitializeService systemInitializeService;
private static final String SUNDRY_PKEY = "sys_initialize";
@Override
public void run(String... args) throws Exception {
initSystemAdmin();
initDefaultNamespace();
}
private void initSystemAdmin() {
clusterInit(SystemInitializeService.GOAL_INIT_ADMIN, Void -> systemInitializeService.initAdmin());
}
private void initDefaultNamespace() {
clusterInit(SystemInitializeService.GOAL_INIT_NAMESPACE, Void -> systemInitializeService.initNamespace());
}
private void clusterInit(String name, Consumer<Void> initFunc) {
Optional<SundryDO> sundryOpt = sundryRepository.findByPkeyAndSkey(SUNDRY_PKEY, name);
if (sundryOpt.isPresent()) {
log.info("[NewSystemInitializer] already initialized, skip: {}", name);
return;
}
String lockName = LOCK_PREFIX.concat(name);
while (true) {
try {
boolean lockStatus = lockService.tryLock(lockName, MAX_LOCK_TIME);
// 无论是否拿到锁都重现检测一次如果已完成初始化则直接 return
Optional<SundryDO> sundryOpt2 = sundryRepository.findByPkeyAndSkey(SUNDRY_PKEY, name);
if (sundryOpt2.isPresent()) {
log.info("[NewSystemInitializer] other server finished initialize, skip process: {}", name);
break;
}
if (!lockStatus) {
CommonUtils.easySleep(277);
continue;
}
log.info("[NewSystemInitializer] try to initialize: {}", name);
initFunc.accept(null);
log.info("[NewSystemInitializer] initialize [{}] successfully!", name);
// 写入初始化成功标记
SundryDO sundryDO = new SundryDO();
sundryDO.setPkey(SUNDRY_PKEY);
sundryDO.setSkey(name);
sundryDO.setContent("A");
sundryDO.setGmtCreate(new Date());
sundryRepository.saveAndFlush(sundryDO);
log.info("[NewSystemInitializer] write initialized tag successfully: {}", sundryDO);
break;
} finally {
lockService.unlock(lockName);
}
}
}
}

View File

@ -0,0 +1,24 @@
package tech.powerjob.server.initializer;
/**
* 系统初始化服务
*
* @author tjq
* @since 2024/2/15
*/
public interface SystemInitializeService {
String GOAL_INIT_ADMIN = "goal_init_admin";
String GOAL_INIT_NAMESPACE = "goal_init_namespace";
/**
* 初始化超级管理员
*/
void initAdmin();
/**
* 初始化 namespace
*/
void initNamespace();
}

View File

@ -0,0 +1,105 @@
package tech.powerjob.server.initializer;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.server.auth.Role;
import tech.powerjob.server.auth.RoleScope;
import tech.powerjob.server.auth.common.AuthConstants;
import tech.powerjob.server.auth.service.login.LoginRequest;
import tech.powerjob.server.auth.service.login.PowerJobLoginService;
import tech.powerjob.server.auth.service.permission.PowerJobPermissionService;
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
import tech.powerjob.server.web.request.ModifyNamespaceRequest;
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
import tech.powerjob.server.web.service.NamespaceWebService;
import tech.powerjob.server.web.service.PwjbUserWebService;
import javax.annotation.Resource;
import javax.transaction.Transactional;
import java.util.Map;
/**
* 初始化 PowerJob 首次部署相关的内容
* 为了可维护性足够高统一使用 WEB 请求进行初始化不直接操作底层防止后续内部逻辑变更后出现问题
*
* @author tjq
* @since 2024/2/15
*/
@Slf4j
@Service
public class SystemInitializeServiceImpl implements SystemInitializeService {
@Value("${oms.auth.initiliaze.admin.password:#{null}}")
private String defaultAdminPassword;
@Resource
private PwjbUserWebService pwjbUserWebService;
@Resource
private NamespaceWebService namespaceWebService;
@Resource
private PowerJobLoginService powerJobLoginService;
@Resource
private PowerJobPermissionService powerJobPermissionService;
private static final String SYSTEM_ADMIN_NAME = "ADMIN";
private static final String SYSTEM_DEFAULT_NAMESPACE = "default_namespace";
@Override
@Transactional(rollbackOn = Exception.class)
public void initAdmin() {
String username = SYSTEM_ADMIN_NAME;
String password = StringUtils.isEmpty(defaultAdminPassword) ? RandomStringUtils.randomAlphabetic(8) : defaultAdminPassword;
// STEP1: 创建 PWJB 用户
ModifyUserInfoRequest createUser = new ModifyUserInfoRequest();
createUser.setUsername(username);
createUser.setNick(username);
createUser.setPassword(password);
log.info("[SystemInitializeService] [S1] create default PWJB user by request: {}", createUser);
PwjbUserInfoDO savedPwjbUser = pwjbUserWebService.save(createUser);
log.info("[SystemInitializeService] [S1] create default PWJB user successfully: {}", savedPwjbUser);
Map<String, Object> params = Maps.newHashMap();
params.put(AuthConstants.PARAM_KEY_USERNAME, username);
params.put(AuthConstants.PARAM_KEY_PASSWORD, password);
// STEP2: 创建 USER 对象
LoginRequest loginRequest = new LoginRequest()
.setLoginType(AuthConstants.ACCOUNT_TYPE_POWER_JOB)
.setOriginParams(JsonUtils.toJSONString(params));
log.info("[SystemInitializeService] [S2] createPowerJobUser user by request: {}", loginRequest);
PowerJobUser powerJobUser = powerJobLoginService.doLogin(loginRequest);
log.info("[SystemInitializeService] [S2] createPowerJobUser successfully: {}", powerJobUser);
// STEP3: 授予全局管理员权限
powerJobPermissionService.grantRole(RoleScope.GLOBAL, AuthConstants.GLOBAL_ADMIN_TARGET_ID, powerJobUser.getId(), Role.ADMIN, null);
log.info("[SystemInitializeService] [S3] GRANT ADMIN successfully!");
// 循环10遍强提醒用户第一次使用必须更改 admin 密码
for (int i = 0; i < 10; i++) {
log.warn("[SystemInitializeService] The system has automatically created a super administrator account[username={},password={}], please log in and change the password immediately!", username, password);
}
}
@Override
@Transactional(rollbackOn = Exception.class)
public void initNamespace() {
ModifyNamespaceRequest saveNamespaceReq = new ModifyNamespaceRequest();
saveNamespaceReq.setName(SYSTEM_DEFAULT_NAMESPACE);
saveNamespaceReq.setCode(SYSTEM_DEFAULT_NAMESPACE);
log.info("[SystemInitializeService] create default namespace by request: {}", saveNamespaceReq);
NamespaceDO savedNamespaceDO = namespaceWebService.save(saveNamespaceReq);
log.info("[SystemInitializeService] create default namespace successfully: {}", savedNamespaceDO);
}
}

View File

@ -1,18 +1,25 @@
package tech.powerjob.server.web.controller;
import com.google.common.collect.Maps;
import org.springframework.web.bind.annotation.*;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.server.auth.PowerJobUser;
import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.server.auth.*;
import tech.powerjob.server.auth.common.AuthConstants;
import tech.powerjob.server.auth.interceptor.ApiPermission;
import tech.powerjob.server.auth.login.LoginTypeInfo;
import tech.powerjob.server.auth.service.login.LoginRequest;
import tech.powerjob.server.auth.service.login.PowerJobLoginService;
import tech.powerjob.server.auth.service.permission.PowerJobPermissionService;
import tech.powerjob.server.web.request.GrantPermissionRequest;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
@ -27,6 +34,8 @@ public class AuthController {
@Resource
private PowerJobLoginService powerJobLoginService;
@Resource
private PowerJobPermissionService powerJobPermissionService;
@GetMapping("/supportLoginTypes")
public ResultDTO<List<LoginTypeInfo>> listSupportLoginTypes() {
@ -87,6 +96,34 @@ public class AuthController {
return powerJobUser.map(ResultDTO::success).orElseGet(() -> ResultDTO.success(null));
}
/* ****************** 授权相关 ****************** */
@PostMapping("/grantAdmin")
@ApiPermission(name = "Auth-GrantAdmin", roleScope = RoleScope.GLOBAL, requiredPermission = Permission.SU)
public ResultDTO<Void> grantAppPermission(GrantPermissionRequest grantPermissionRequest) {
grantPermissionRequest.setRole(Role.ADMIN.getV());
grantPermissionRequest.setTargetId(AuthConstants.GLOBAL_ADMIN_TARGET_ID);
grantPermission(RoleScope.GLOBAL, grantPermissionRequest);
return ResultDTO.success(null);
}
private void grantPermission(RoleScope roleScope, GrantPermissionRequest grantPermissionRequest) {
Role role = Role.of(grantPermissionRequest.getRole());
Optional.ofNullable(grantPermissionRequest.getUserIds()).orElse(Collections.emptyList()).forEach(uid -> {
// 记录授权人信息
Map<String, Object> extraInfo = Maps.newHashMap();
extraInfo.put("grantor", LoginUserHolder.getUserName());
String extra = JsonUtils.toJSONString(extraInfo);
powerJobPermissionService.grantRole(roleScope, grantPermissionRequest.getTargetId(), uid, role, extra);
});
}
private void fillJwt4LoginUser(PowerJobUser powerJobUser, HttpServletResponse httpServletResponse) {
httpServletResponse.addCookie(new Cookie(AuthConstants.JWT_NAME, powerJobUser.getJwtToken()));
}

View File

@ -1,17 +1,9 @@
package tech.powerjob.server.web.controller;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.bind.annotation.*;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.server.auth.LoginUserHolder;
import tech.powerjob.server.auth.Permission;
import tech.powerjob.server.auth.RoleScope;
import tech.powerjob.server.auth.common.AuthConstants;
@ -19,27 +11,18 @@ import tech.powerjob.server.auth.interceptor.ApiPermission;
import tech.powerjob.server.auth.plugin.ModifyOrCreateDynamicPermission;
import tech.powerjob.server.auth.plugin.SaveNamespaceGrantPermissionPlugin;
import tech.powerjob.server.auth.service.WebAuthService;
import tech.powerjob.server.common.SJ;
import tech.powerjob.server.common.constants.SwitchableStatus;
import tech.powerjob.server.persistence.PageResult;
import tech.powerjob.server.persistence.QueryConvertUtils;
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
import tech.powerjob.server.persistence.remote.repository.NamespaceRepository;
import tech.powerjob.server.web.converter.NamespaceConverter;
import tech.powerjob.server.web.request.ComponentUserRoleInfo;
import tech.powerjob.server.web.request.ModifyNamespaceRequest;
import tech.powerjob.server.web.request.QueryNamespaceRequest;
import tech.powerjob.server.web.response.NamespaceBaseVO;
import tech.powerjob.server.web.response.NamespaceVO;
import tech.powerjob.server.web.service.NamespaceWebService;
import javax.annotation.Resource;
import javax.persistence.criteria.Predicate;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
/**
@ -56,67 +39,21 @@ public class NamespaceController {
@Resource
private WebAuthService webAuthService;
@Resource
private AppInfoRepository appInfoRepository;
@Resource
private NamespaceRepository namespaceRepository;
private NamespaceWebService namespaceWebService;
@ResponseBody
@PostMapping("/save")
@ApiPermission(name = "Namespace-Save", roleScope = RoleScope.NAMESPACE, dynamicPermissionPlugin = ModifyOrCreateDynamicPermission.class, grandPermissionPlugin = SaveNamespaceGrantPermissionPlugin.class)
public ResultDTO<NamespaceVO> save(@RequestBody ModifyNamespaceRequest req) {
req.valid();
Long id = req.getId();
NamespaceDO namespaceDO;
boolean isCreate = id == null;
if (isCreate) {
namespaceDO = new NamespaceDO();
namespaceDO.setGmtCreate(new Date());
// code 单独拷贝
namespaceDO.setCode(req.getCode());
// 创建时生成 token
namespaceDO.setToken(UUID.randomUUID().toString());
namespaceDO.setCreator(LoginUserHolder.getUserName());
} else {
namespaceDO = fetchById(id);
namespaceDO.setModifier(LoginUserHolder.getUserName());
if (!namespaceDO.getCode().equalsIgnoreCase(req.getCode())) {
throw new IllegalArgumentException("NOT_ALLOW_CHANGE_THE_NAMESPACE_CODE");
}
}
// 拷贝通用变更属性code 不允许更改
namespaceDO.setTags(req.getTags());
namespaceDO.setName(req.getName());
namespaceDO.setExtra(req.getExtra());
namespaceDO.setStatus(Optional.ofNullable(req.getStatus()).orElse(SwitchableStatus.ENABLE.getV()));
namespaceDO.setGmtModified(new Date());
NamespaceDO savedNamespace = namespaceRepository.save(namespaceDO);
// 授权
webAuthService.processPermissionOnSave(RoleScope.NAMESPACE, savedNamespace.getId(), req.getComponentUserRoleInfo());
NamespaceDO savedNamespace = namespaceWebService.save(req);
return ResultDTO.success(NamespaceConverter.do2BaseVo(savedNamespace));
}
@DeleteMapping("/delete")
@ApiPermission(name = "Namespace-Delete", roleScope = RoleScope.NAMESPACE, requiredPermission = Permission.SU)
public ResultDTO<Void> deleteNamespace(Long id) {
List<AppInfoDO> appInfosInNamespace = appInfoRepository.findAllByNamespaceId(id);
if (CollectionUtils.isNotEmpty(appInfosInNamespace)) {
List<String> relatedApps = appInfosInNamespace.stream().map(AppInfoDO::getAppName).collect(Collectors.toList());
throw new PowerJobException("Unable to delete due to associated apps: " + SJ.COMMA_JOINER.join(relatedApps));
}
namespaceRepository.deleteById(id);
namespaceWebService.delete(id);
return ResultDTO.success(null);
}
@ -124,33 +61,7 @@ public class NamespaceController {
@ApiPermission(name = "Namespace-List", roleScope = RoleScope.NAMESPACE, requiredPermission = Permission.NONE)
public ResultDTO<PageResult<NamespaceVO>> listNamespace(@RequestBody QueryNamespaceRequest queryNamespaceRequest) {
String codeLike = queryNamespaceRequest.getCodeLike();
String nameLike = queryNamespaceRequest.getNameLike();
String tagLike = queryNamespaceRequest.getTagLike();
Pageable pageable = PageRequest.of(queryNamespaceRequest.getIndex(), queryNamespaceRequest.getPageSize());
Specification<NamespaceDO> specification = (root, query, cb) -> {
List<Predicate> predicates = Lists.newArrayList();
if (StringUtils.isNotEmpty(codeLike)) {
predicates.add(cb.like(root.get("code"), QueryConvertUtils.convertLikeParams(codeLike)));
}
if (StringUtils.isNotEmpty(nameLike)) {
predicates.add(cb.like(root.get("name"), QueryConvertUtils.convertLikeParams(nameLike)));
}
if (StringUtils.isNotEmpty(tagLike)) {
predicates.add(cb.like(root.get("tags"), QueryConvertUtils.convertLikeParams(tagLike)));
}
if (predicates.isEmpty()) {
return null;
}
return query.where(predicates.toArray(new Predicate[0])).getRestriction();
};
Page<NamespaceDO> namespacePageResult = namespaceRepository.findAll(specification, pageable);
Page<NamespaceDO> namespacePageResult = namespaceWebService.list(queryNamespaceRequest);
PageResult<NamespaceVO> ret = new PageResult<>(namespacePageResult);
ret.setData(namespacePageResult.get().map(x -> {
@ -166,7 +77,7 @@ public class NamespaceController {
@ApiPermission(name = "Namespace-ListAll", roleScope = RoleScope.NAMESPACE, requiredPermission = Permission.NONE)
public ResultDTO<List<NamespaceBaseVO>> listAll() {
// 数量应该不是很多先简单处理不查询精简对象
List<NamespaceDO> namespaceRepositoryAll = namespaceRepository.findAll();
List<NamespaceDO> namespaceRepositoryAll = namespaceWebService.listAll();
List<NamespaceBaseVO> namespaceBaseVOList = namespaceRepositoryAll.stream().map(nd -> {
NamespaceBaseVO nv = new NamespaceBaseVO();
nv.setId(nd.getId());
@ -191,12 +102,4 @@ public class NamespaceController {
namespaceVO.setToken(hasPermission ? namespaceDO.getToken() : AuthConstants.TIPS_NO_PERMISSION_TO_SEE);
}
private NamespaceDO fetchById(Long id) {
Optional<NamespaceDO> namespaceDoOpt = namespaceRepository.findById(id);
if (!namespaceDoOpt.isPresent()) {
throw new IllegalArgumentException("can't find namespace by id: " + id);
}
return namespaceDoOpt.get();
}
}

View File

@ -1,24 +1,15 @@
package tech.powerjob.server.web.controller;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.common.utils.CommonUtils;
import tech.powerjob.server.auth.common.AuthErrorCode;
import tech.powerjob.server.auth.common.PowerJobAuthException;
import tech.powerjob.server.common.utils.DigestUtils;
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
import tech.powerjob.server.persistence.remote.repository.PwjbUserInfoRepository;
import tech.powerjob.server.web.request.ChangePasswordRequest;
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
import tech.powerjob.server.web.service.PwjbUserWebService;
import javax.annotation.Resource;
import java.util.Date;
import java.util.Optional;
/**
* PowerJob 自带的登录体系
@ -32,7 +23,7 @@ import java.util.Optional;
public class PwjbUserInfoController {
@Resource
private PwjbUserInfoRepository pwjbUserInfoRepository;
private PwjbUserWebService pwjbUserWebService;
/**
* 创建第三方登录体系PowerJob 的账户不允许修改
@ -41,58 +32,14 @@ public class PwjbUserInfoController {
*/
@PostMapping("/create")
public ResultDTO<Void> save(@RequestBody ModifyUserInfoRequest request) {
String username = request.getUsername();
CommonUtils.requireNonNull(username, "userName can't be null or empty!");
Optional<PwjbUserInfoDO> oldUserOpt = pwjbUserInfoRepository.findByUsername(username);
if (oldUserOpt.isPresent()) {
throw new IllegalArgumentException("username already exist, please change one!");
}
PwjbUserInfoDO pwjbUserInfoDO = new PwjbUserInfoDO();
pwjbUserInfoDO.setUsername(username);
pwjbUserInfoDO.setGmtCreate(new Date());
pwjbUserInfoDO.setGmtModified(new Date());
// 二次加密密码
final String password = request.getPassword();
if (StringUtils.isNotEmpty(password)) {
pwjbUserInfoDO.setPassword(DigestUtils.rePassword(password, pwjbUserInfoDO.getUsername()));
}
// 其他参数存入 extra在回调创建真正的内部 USER 时回填
request.setPassword(null);
pwjbUserInfoDO.setExtra(JsonUtils.toJSONString(request));
pwjbUserInfoRepository.save(pwjbUserInfoDO);
pwjbUserWebService.save(request);
return ResultDTO.success(null);
}
@PostMapping("/changePassword")
public ResultDTO<Void> changePassword(@RequestBody ChangePasswordRequest changePasswordRequest) {
if (StringUtils.equals(changePasswordRequest.getNewPassword(), changePasswordRequest.getNewPassword2())) {
throw new IllegalArgumentException("Inconsistent passwords");
}
String username = changePasswordRequest.getUsername();
Optional<PwjbUserInfoDO> userOpt = pwjbUserInfoRepository.findByUsername(username);
if (!userOpt.isPresent()) {
throw new IllegalArgumentException("can't find user by username: " + username);
}
PwjbUserInfoDO dbUser = userOpt.get();
String oldPasswordInDb = dbUser.getPassword();
String oldPasswordInReq = DigestUtils.rePassword(changePasswordRequest.getOldPassword(), dbUser.getUsername());
if (!StringUtils.equals(oldPasswordInDb, oldPasswordInReq)) {
throw new PowerJobAuthException(AuthErrorCode.INCORRECT_PASSWORD);
}
dbUser.setPassword(DigestUtils.rePassword(changePasswordRequest.getNewPassword(), dbUser.getUsername()));
dbUser.setGmtModified(new Date());
pwjbUserInfoRepository.saveAndFlush(dbUser);
pwjbUserWebService.changePassword(changePasswordRequest);
return ResultDTO.success(null);
}
}

View File

@ -0,0 +1,25 @@
package tech.powerjob.server.web.service;
import org.springframework.data.domain.Page;
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
import tech.powerjob.server.web.request.ModifyNamespaceRequest;
import tech.powerjob.server.web.request.QueryNamespaceRequest;
import java.util.List;
/**
* namespace web 服务
*
* @author tjq
* @since 2024/2/15
*/
public interface NamespaceWebService {
NamespaceDO save(ModifyNamespaceRequest req);
void delete(Long id);
Page<NamespaceDO> list(QueryNamespaceRequest queryNamespaceRequest);
List<NamespaceDO> listAll();
}

View File

@ -0,0 +1,18 @@
package tech.powerjob.server.web.service;
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
import tech.powerjob.server.web.request.ChangePasswordRequest;
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
/**
* PwjbUserWebService
*
* @author tjq
* @since 2024/2/15
*/
public interface PwjbUserWebService {
PwjbUserInfoDO save(ModifyUserInfoRequest request);
void changePassword(ChangePasswordRequest changePasswordRequest);
}

View File

@ -0,0 +1,147 @@
package tech.powerjob.server.web.service.impl;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.server.auth.LoginUserHolder;
import tech.powerjob.server.auth.RoleScope;
import tech.powerjob.server.auth.service.WebAuthService;
import tech.powerjob.server.common.SJ;
import tech.powerjob.server.common.constants.SwitchableStatus;
import tech.powerjob.server.persistence.QueryConvertUtils;
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
import tech.powerjob.server.persistence.remote.model.NamespaceDO;
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
import tech.powerjob.server.persistence.remote.repository.NamespaceRepository;
import tech.powerjob.server.web.request.ModifyNamespaceRequest;
import tech.powerjob.server.web.request.QueryNamespaceRequest;
import tech.powerjob.server.web.service.NamespaceWebService;
import javax.annotation.Resource;
import javax.persistence.criteria.Predicate;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* NamespaceWebService
*
* @author tjq
* @since 2024/2/15
*/
@Service
public class NamespaceWebServiceImpl implements NamespaceWebService {
@Resource
private WebAuthService webAuthService;
@Resource
private AppInfoRepository appInfoRepository;
@Resource
private NamespaceRepository namespaceRepository;
@Override
public NamespaceDO save(ModifyNamespaceRequest req) {
req.valid();
Long id = req.getId();
NamespaceDO namespaceDO;
boolean isCreate = id == null;
if (isCreate) {
namespaceDO = new NamespaceDO();
namespaceDO.setGmtCreate(new Date());
// code 单独拷贝
namespaceDO.setCode(req.getCode());
// 创建时生成 token
namespaceDO.setToken(UUID.randomUUID().toString());
namespaceDO.setCreator(LoginUserHolder.getUserName());
} else {
namespaceDO = fetchById(id);
namespaceDO.setModifier(LoginUserHolder.getUserName());
if (!namespaceDO.getCode().equalsIgnoreCase(req.getCode())) {
throw new IllegalArgumentException("NOT_ALLOW_CHANGE_THE_NAMESPACE_CODE");
}
}
// 拷贝通用变更属性code 不允许更改
namespaceDO.setTags(req.getTags());
namespaceDO.setName(req.getName());
namespaceDO.setExtra(req.getExtra());
namespaceDO.setStatus(Optional.ofNullable(req.getStatus()).orElse(SwitchableStatus.ENABLE.getV()));
namespaceDO.setGmtModified(new Date());
NamespaceDO savedNamespace = namespaceRepository.save(namespaceDO);
// 授权
webAuthService.processPermissionOnSave(RoleScope.NAMESPACE, savedNamespace.getId(), req.getComponentUserRoleInfo());
return savedNamespace;
}
@Override
public void delete(Long id) {
List<AppInfoDO> appInfosInNamespace = appInfoRepository.findAllByNamespaceId(id);
if (CollectionUtils.isNotEmpty(appInfosInNamespace)) {
List<String> relatedApps = appInfosInNamespace.stream().map(AppInfoDO::getAppName).collect(Collectors.toList());
throw new PowerJobException("Unable to delete due to associated apps: " + SJ.COMMA_JOINER.join(relatedApps));
}
namespaceRepository.deleteById(id);
}
@Override
public Page<NamespaceDO> list(QueryNamespaceRequest queryNamespaceRequest) {
String codeLike = queryNamespaceRequest.getCodeLike();
String nameLike = queryNamespaceRequest.getNameLike();
String tagLike = queryNamespaceRequest.getTagLike();
Pageable pageable = PageRequest.of(queryNamespaceRequest.getIndex(), queryNamespaceRequest.getPageSize());
Specification<NamespaceDO> specification = (root, query, cb) -> {
List<Predicate> predicates = Lists.newArrayList();
if (StringUtils.isNotEmpty(codeLike)) {
predicates.add(cb.like(root.get("code"), QueryConvertUtils.convertLikeParams(codeLike)));
}
if (StringUtils.isNotEmpty(nameLike)) {
predicates.add(cb.like(root.get("name"), QueryConvertUtils.convertLikeParams(nameLike)));
}
if (StringUtils.isNotEmpty(tagLike)) {
predicates.add(cb.like(root.get("tags"), QueryConvertUtils.convertLikeParams(tagLike)));
}
if (predicates.isEmpty()) {
return null;
}
return query.where(predicates.toArray(new Predicate[0])).getRestriction();
};
return namespaceRepository.findAll(specification, pageable);
}
@Override
public List<NamespaceDO> listAll() {
return namespaceRepository.findAll();
}
private NamespaceDO fetchById(Long id) {
Optional<NamespaceDO> namespaceDoOpt = namespaceRepository.findById(id);
if (!namespaceDoOpt.isPresent()) {
throw new IllegalArgumentException("can't find namespace by id: " + id);
}
return namespaceDoOpt.get();
}
}

View File

@ -0,0 +1,88 @@
package tech.powerjob.server.web.service.impl;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.common.utils.CommonUtils;
import tech.powerjob.server.auth.common.AuthErrorCode;
import tech.powerjob.server.auth.common.PowerJobAuthException;
import tech.powerjob.server.common.utils.DigestUtils;
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
import tech.powerjob.server.persistence.remote.repository.PwjbUserInfoRepository;
import tech.powerjob.server.web.request.ChangePasswordRequest;
import tech.powerjob.server.web.request.ModifyUserInfoRequest;
import tech.powerjob.server.web.service.PwjbUserWebService;
import javax.annotation.Resource;
import java.util.Date;
import java.util.Optional;
/**
* PwjbUserWebService
*
* @author tjq
* @since 2024/2/15
*/
@Service
public class PwjbUserWebServiceImplImpl implements PwjbUserWebService {
@Resource
private PwjbUserInfoRepository pwjbUserInfoRepository;
@Override
@SneakyThrows
public PwjbUserInfoDO save(ModifyUserInfoRequest request) {
String username = request.getUsername();
CommonUtils.requireNonNull(username, "userName can't be null or empty!");
Optional<PwjbUserInfoDO> oldUserOpt = pwjbUserInfoRepository.findByUsername(username);
if (oldUserOpt.isPresent()) {
throw new IllegalArgumentException("username already exist, please change one!");
}
PwjbUserInfoDO pwjbUserInfoDO = new PwjbUserInfoDO();
pwjbUserInfoDO.setUsername(username);
pwjbUserInfoDO.setGmtCreate(new Date());
pwjbUserInfoDO.setGmtModified(new Date());
// 二次加密密码
final String password = request.getPassword();
if (StringUtils.isNotEmpty(password)) {
pwjbUserInfoDO.setPassword(DigestUtils.rePassword(password, pwjbUserInfoDO.getUsername()));
}
// 其他参数存入 extra在回调创建真正的内部 USER 时回填
ModifyUserInfoRequest cpRequest = JsonUtils.parseObject(JsonUtils.toJSONString(request), ModifyUserInfoRequest.class);
cpRequest.setPassword(null);
cpRequest.setUsername(null);
cpRequest.setNick(null);
pwjbUserInfoDO.setExtra(JsonUtils.toJSONString(cpRequest));
return pwjbUserInfoRepository.save(pwjbUserInfoDO);
}
@Override
public void changePassword(ChangePasswordRequest changePasswordRequest) {
if (!StringUtils.equals(changePasswordRequest.getNewPassword(), changePasswordRequest.getNewPassword2())) {
throw new IllegalArgumentException("Inconsistent passwords");
}
String username = changePasswordRequest.getUsername();
Optional<PwjbUserInfoDO> userOpt = pwjbUserInfoRepository.findByUsername(username);
if (!userOpt.isPresent()) {
throw new IllegalArgumentException("can't find user by username: " + username);
}
PwjbUserInfoDO dbUser = userOpt.get();
String oldPasswordInDb = dbUser.getPassword();
String oldPasswordInReq = DigestUtils.rePassword(changePasswordRequest.getOldPassword(), dbUser.getUsername());
if (!StringUtils.equals(oldPasswordInDb, oldPasswordInReq)) {
throw new PowerJobAuthException(AuthErrorCode.INCORRECT_PASSWORD);
}
dbUser.setPassword(DigestUtils.rePassword(changePasswordRequest.getNewPassword(), dbUser.getUsername()));
dbUser.setGmtModified(new Date());
pwjbUserInfoRepository.saveAndFlush(dbUser);
}
}

View File

@ -0,0 +1,9 @@
/**
* 处理 WEB 服务的 service
* 如果有共用逻辑可以单独抽成 service否则直接写在 controller 即可PowerJob WEB 领域模型不复杂没必要过度封装
* LESS IS MORE
*
* @author tjq
* @since 2024/2/15
*/
package tech.powerjob.server.web.service;