mirror of
https://github.com/PowerJob/PowerJob.git
synced 2025-07-17 00:00:04 +08:00
feat: NewSystemInitializer and token verify
This commit is contained in:
parent
9419340829
commit
686189e6ca
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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"),
|
||||
|
||||
;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,11 @@ public class ThirdPartyUser {
|
||||
* 用户的唯一标识,用于关联到 PowerJob 的 username
|
||||
*/
|
||||
private String username;
|
||||
/**
|
||||
* JWT 登录的二次校验配置
|
||||
* 可空,空则代表放弃二次校验(会出现第三方登录改了密码当 PowerJob JWT 登录依然可用的情况)
|
||||
*/
|
||||
private TokenLoginVerifyInfo tokenLoginVerifyInfo;
|
||||
|
||||
/* ******** 以下全部选填即可,只是方便数据同步,后续都可以去 PowerJob 控制台更改 ******** */
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -25,5 +25,8 @@ public class LoginRequest {
|
||||
*/
|
||||
private String originParams;
|
||||
|
||||
/**
|
||||
* http原始请求,第三方回调参数传递类型无法枚举,直接传递 HttpServletRequest 满足扩展性要求
|
||||
*/
|
||||
private transient HttpServletRequest httpServletRequest;
|
||||
}
|
||||
|
@ -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) {
|
||||
// header、cookie 都能获取
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
* 获取有相关权限的用户
|
||||
|
@ -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 -> {
|
||||
|
@ -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;
|
||||
}
|
@ -54,6 +54,12 @@ public class UserInfoDO {
|
||||
* webHook
|
||||
*/
|
||||
private String webHook;
|
||||
|
||||
/**
|
||||
* JWT 登录的二次校验信息
|
||||
*/
|
||||
private String tokenLoginVerifyInfo;
|
||||
|
||||
/**
|
||||
* 扩展字段 for 第三方
|
||||
* PowerJob 内部不允许使用该字段
|
||||
|
@ -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);
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
Loading…
x
Reference in New Issue
Block a user