mirror of
https://github.com/PowerJob/PowerJob.git
synced 2025-07-17 00:00:04 +08:00
feat: [auth] design ThirdPartyLoginService
This commit is contained in:
parent
4793c19af6
commit
cda55c918b
@ -22,6 +22,7 @@
|
||||
<module>powerjob-server-migrate</module>
|
||||
<module>powerjob-server-core</module>
|
||||
<module>powerjob-server-monitor</module>
|
||||
<module>powerjob-server-auth</module>
|
||||
</modules>
|
||||
|
||||
|
||||
|
58
powerjob-server/powerjob-server-auth/pom.xml
Normal file
58
powerjob-server/powerjob-server-auth/pom.xml
Normal file
@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<parent>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server</artifactId>
|
||||
<version>4.3.7</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>powerjob-server-auth</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>8</maven.compiler.source>
|
||||
<maven.compiler.target>8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<jjwt.version>0.11.5</jjwt.version>
|
||||
<dingtalk.version>1.1.86</dingtalk.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>tech.powerjob</groupId>
|
||||
<artifactId>powerjob-server-persistence</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>${jjwt.version}</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.aliyun</groupId>
|
||||
<artifactId>dingtalk</artifactId>
|
||||
<version>${dingtalk.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
||||
</project>
|
@ -0,0 +1,40 @@
|
||||
package tech.powerjob.server.auth;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 权限
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum Permission {
|
||||
|
||||
/**
|
||||
* 不需要权限
|
||||
*/
|
||||
NONE(1),
|
||||
/**
|
||||
* 读权限,查看控制台数据
|
||||
*/
|
||||
READ(10),
|
||||
/**
|
||||
* 写权限,新增/修改任务等
|
||||
*/
|
||||
WRITE(20),
|
||||
/**
|
||||
* 运维权限,比如任务的执行
|
||||
*/
|
||||
OPS(30),
|
||||
/**
|
||||
* 超级权限
|
||||
*/
|
||||
SU(100)
|
||||
;
|
||||
|
||||
|
||||
private int v;
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package tech.powerjob.server.auth;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static tech.powerjob.server.auth.Permission.*;
|
||||
|
||||
/**
|
||||
* 角色
|
||||
* PowerJob 采用 RBAC 实现权限,出于实际需求的考虑,不决定采用动态权限模型。因此 RBAC 中的角色和权限均在此处定义。
|
||||
* 如果有自定义诉求,可以修改 Role 的定义
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum Role {
|
||||
|
||||
/**
|
||||
* 观察者,默认只读权限
|
||||
*/
|
||||
OBSERVER(10, Sets.newHashSet(READ)),
|
||||
/**
|
||||
* 技术质量,读 + 操作权限
|
||||
*/
|
||||
QA(20, Sets.newHashSet(READ, OPS)),
|
||||
/**
|
||||
* 开发者,读 + 编辑 + 操作权限
|
||||
*/
|
||||
DEVELOPER(30, Sets.newHashSet(READ, WRITE, OPS)),
|
||||
/**
|
||||
* 管理员
|
||||
*/
|
||||
ADMIN(40, Sets.newHashSet(READ, WRITE, OPS, SU))
|
||||
;
|
||||
|
||||
private final int v;
|
||||
|
||||
private final Set<Permission> permissions;
|
||||
|
||||
public static Role of(int vv) {
|
||||
for (Role role : values()) {
|
||||
if (vv == role.v) {
|
||||
return role;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("unknown role: " + vv);
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package tech.powerjob.server.auth;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 权限范围
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/9/3
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum RoleScope {
|
||||
|
||||
/**
|
||||
* NAMESPACE 权限
|
||||
*/
|
||||
NAMESPACE(1),
|
||||
/**
|
||||
* APP 级别权限
|
||||
*/
|
||||
APP(10),
|
||||
/**
|
||||
* 全局权限
|
||||
*/
|
||||
GLOBAL(666)
|
||||
;
|
||||
|
||||
private final int v;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package tech.powerjob.server.auth.login;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 登录上下文
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/10
|
||||
*/
|
||||
@Data
|
||||
public class LoginContext {
|
||||
|
||||
/**
|
||||
* 原始参数,给第三方登录方式一个服务端和前端交互的数据通道。PowerJob 本身不感知其中的内容
|
||||
*/
|
||||
private String originParams;
|
||||
|
||||
private transient HttpServletRequest httpServletRequest;
|
||||
|
||||
private transient HttpServletResponse httpServletResponse;
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package tech.powerjob.server.auth.login;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 登录类型描述
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/10
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class LoginTypeInfo implements Serializable {
|
||||
|
||||
/**
|
||||
* 登录类型,唯一标识
|
||||
*/
|
||||
private String type;
|
||||
/**
|
||||
* 描述名称,前端展示用
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 展示用的 ICON
|
||||
*/
|
||||
private String iconUrl;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package tech.powerjob.server.auth.login;
|
||||
|
||||
/**
|
||||
* 第三方登录服务
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/10
|
||||
*/
|
||||
public interface ThirdPartyLoginService {
|
||||
|
||||
/**
|
||||
* 登陆服务的类型
|
||||
* @return 登陆服务类型,比如 PowerJob / DingTalk
|
||||
*/
|
||||
LoginTypeInfo loginType();
|
||||
|
||||
/**
|
||||
* 生成登陆的重定向 URL
|
||||
* @param loginContext 上下文
|
||||
* @return 重定向地址
|
||||
*/
|
||||
String generateLoginUrl(LoginContext loginContext);
|
||||
|
||||
/**
|
||||
* 执行第三方登录
|
||||
* @param loginContext 上下文
|
||||
* @return 登录地址
|
||||
*/
|
||||
ThirdPartyUser login(LoginContext loginContext);
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package tech.powerjob.server.auth.login;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 第三方用户
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2024/2/10
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class ThirdPartyUser {
|
||||
|
||||
/**
|
||||
* 用户的唯一标识,用于关联到 PowerJob 的 username
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/* ******** 以下全部选填即可,只是方便数据同步,后续都可以去 PowerJob 控制台更改 ******** */
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String nick;
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String phone;
|
||||
/**
|
||||
* 邮箱地址
|
||||
*/
|
||||
private String email;
|
||||
/**
|
||||
* 扩展字段
|
||||
*/
|
||||
private String extra;
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
package tech.powerjob.server.auth.login.impl;
|
||||
|
||||
import com.aliyun.dingtalkcontact_1_0.models.GetUserHeaders;
|
||||
import com.aliyun.dingtalkcontact_1_0.models.GetUserResponseBody;
|
||||
import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenRequest;
|
||||
import com.aliyun.dingtalkoauth2_1_0.models.GetUserTokenResponse;
|
||||
import com.aliyun.teaopenapi.models.Config;
|
||||
import com.aliyun.teautil.models.RuntimeOptions;
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.server.auth.login.LoginContext;
|
||||
import tech.powerjob.server.auth.login.LoginTypeInfo;
|
||||
import tech.powerjob.server.auth.login.ThirdPartyLoginService;
|
||||
import tech.powerjob.server.auth.login.ThirdPartyUser;
|
||||
import tech.powerjob.server.common.Loggers;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* <a href="https://open.dingtalk.com/document/orgapp/tutorial-obtaining-user-personal-information">钉钉账号体系登录第三方网站</a>
|
||||
* PowerJob 官方支持钉钉账号体系登录原因:
|
||||
* 1. 钉钉作为当下用户体量最大的企业级办公软件,覆盖率足够高,提供钉钉支持能让更多开发者开箱即用
|
||||
* 2. 钉钉的 API 设计和 PowerJob 设想一致,算是个最佳实践,其他企业内部的账号体系可参考这套流程进行接入
|
||||
* - PowerJob 重定向到第三方账号体系登陆页 -> 第三方完成登陆 -> 跳转回调 PowerJob auth 接口 -> PowerJob 解析回调登陆信息,完整用户关联
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/26
|
||||
*/
|
||||
public class DingTalkLoginService implements ThirdPartyLoginService {
|
||||
|
||||
/*
|
||||
配置示例
|
||||
oms.auth.dingtalk.appkey=dinggzqqzqqzqqzqq
|
||||
oms.auth.dingtalk.appSecret=iY-FS8mzqqzqq_xEizqqzqqzqqzqqzqqzqqYEbkZOal
|
||||
oms.auth.dingtalk.callbackUrl=http://localhost:7700/auth/loginCallback
|
||||
*/
|
||||
|
||||
/**
|
||||
* 钉钉应用 AppKey
|
||||
*/
|
||||
@Value("${oms.auth.dingtalk.appkey:#{null}}")
|
||||
private String dingTalkAppKey;
|
||||
/**
|
||||
* 钉钉应用 AppSecret
|
||||
*/
|
||||
@Value("${oms.auth.dingtalk.appSecret:#{null}}")
|
||||
private String dingTalkAppSecret;
|
||||
/**
|
||||
* 回调地址,powerjob-server 地址 + /user/auth
|
||||
* 比如本地调试时为 <a href="http://localhost:7700/auth/loginCallback">LocalDemoCallbackUrl</a>
|
||||
* 部署后则为 <a href="http://try.powerjob.tech/auth/loginCallback">demoCallBackUrl</a>
|
||||
*/
|
||||
@Value("${oms.auth.dingtalk.callbackUrl:#{null}}")
|
||||
private String dingTalkCallbackUrl;
|
||||
|
||||
private static final String DING_TALK = "DingTalk";
|
||||
|
||||
@Override
|
||||
public LoginTypeInfo loginType() {
|
||||
return new LoginTypeInfo()
|
||||
.setType(DING_TALK)
|
||||
.setName("钉钉登录")
|
||||
;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String generateLoginUrl(LoginContext loginContext) {
|
||||
if (StringUtils.isAnyEmpty(dingTalkAppKey, dingTalkAppSecret, dingTalkCallbackUrl)) {
|
||||
throw new IllegalArgumentException("please config 'oms.auth.dingtalk.appkey', 'oms.auth.dingtalk.appSecret' and 'oms.auth.dingtalk.callbackUrl' in properties!");
|
||||
}
|
||||
|
||||
String urlString = URLEncoder.encode(dingTalkCallbackUrl, StandardCharsets.UTF_8.name());
|
||||
String url = "https://login.dingtalk.com/oauth2/auth?" +
|
||||
"redirect_uri=" + urlString +
|
||||
"&response_type=code" +
|
||||
"&client_id=" + dingTalkAppKey +
|
||||
"&scope=openid" +
|
||||
"&state=" + DING_TALK +
|
||||
"&prompt=consent";
|
||||
Loggers.WEB.info("[DingTalkBizLoginService] login url: {}", url);
|
||||
return url;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public ThirdPartyUser login(LoginContext loginContext) {
|
||||
try {
|
||||
com.aliyun.dingtalkoauth2_1_0.Client client = authClient();
|
||||
GetUserTokenRequest getUserTokenRequest = new GetUserTokenRequest()
|
||||
//应用基础信息-应用信息的AppKey,请务必替换为开发的应用AppKey
|
||||
.setClientId(dingTalkAppKey)
|
||||
//应用基础信息-应用信息的AppSecret,,请务必替换为开发的应用AppSecret
|
||||
.setClientSecret(dingTalkAppSecret)
|
||||
.setCode(loginContext.getHttpServletRequest().getParameter("authCode"))
|
||||
.setGrantType("authorization_code");
|
||||
GetUserTokenResponse getUserTokenResponse = client.getUserToken(getUserTokenRequest);
|
||||
//获取用户个人 token
|
||||
String accessToken = getUserTokenResponse.getBody().getAccessToken();
|
||||
// 查询钉钉用户
|
||||
final GetUserResponseBody dingUser = getUserinfo(accessToken);
|
||||
// 将钉钉用户的唯一ID 和 PowerJob 账户体系的唯一键 username 关联
|
||||
if (dingUser != null) {
|
||||
ThirdPartyUser bizUser = new ThirdPartyUser();
|
||||
bizUser.setUsername(dingUser.getUnionId());
|
||||
bizUser.setNick(dingUser.getNick());
|
||||
bizUser.setPhone(dingUser.getMobile());
|
||||
bizUser.setEmail(dingUser.getEmail());
|
||||
return bizUser;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Loggers.WEB.error("[DingTalkBizLoginService] login by dingTalk failed!", e);
|
||||
throw e;
|
||||
}
|
||||
throw new PowerJobException("login from dingTalk failed!");
|
||||
}
|
||||
|
||||
/* 以下代码均拷自钉钉官网示例 */
|
||||
|
||||
private static com.aliyun.dingtalkoauth2_1_0.Client authClient() throws Exception {
|
||||
Config config = new Config();
|
||||
config.protocol = "https";
|
||||
config.regionId = "central";
|
||||
return new com.aliyun.dingtalkoauth2_1_0.Client(config);
|
||||
}
|
||||
private static com.aliyun.dingtalkcontact_1_0.Client contactClient() throws Exception {
|
||||
Config config = new Config();
|
||||
config.protocol = "https";
|
||||
config.regionId = "central";
|
||||
return new com.aliyun.dingtalkcontact_1_0.Client(config);
|
||||
}
|
||||
|
||||
private GetUserResponseBody getUserinfo(String accessToken) throws Exception {
|
||||
com.aliyun.dingtalkcontact_1_0.Client client = contactClient();
|
||||
GetUserHeaders getUserHeaders = new GetUserHeaders();
|
||||
getUserHeaders.xAcsDingtalkAccessToken = accessToken;
|
||||
//获取用户个人信息,如需获取当前授权人的信息,unionId参数必须传me
|
||||
return client.getUserWithOptions("me", getUserHeaders, new RuntimeOptions()).getBody();
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package tech.powerjob.server.auth.login.impl;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import tech.powerjob.common.exception.PowerJobException;
|
||||
import tech.powerjob.server.auth.login.LoginContext;
|
||||
import tech.powerjob.server.auth.login.LoginTypeInfo;
|
||||
import tech.powerjob.server.auth.login.ThirdPartyLoginService;
|
||||
import tech.powerjob.server.auth.login.ThirdPartyUser;
|
||||
import tech.powerjob.server.common.Loggers;
|
||||
import tech.powerjob.server.common.SJ;
|
||||
import tech.powerjob.server.common.utils.DigestUtils;
|
||||
import tech.powerjob.server.persistence.remote.model.UserInfoDO;
|
||||
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* PowerJob 自带的登陆服务
|
||||
* 和应用主框架无关,依然属于第三方登录体系
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
@Service
|
||||
public class PowerJobLoginService implements ThirdPartyLoginService {
|
||||
|
||||
@Resource
|
||||
private UserInfoRepository userInfoRepository;
|
||||
|
||||
private static final String POWER_JOB_LOGIN_SERVICE = "PowerJob";
|
||||
|
||||
private static final String KEY_USERNAME = "username";
|
||||
private static final String KEY_PASSWORD = "password";
|
||||
|
||||
@Override
|
||||
public LoginTypeInfo loginType() {
|
||||
return new LoginTypeInfo()
|
||||
.setType(POWER_JOB_LOGIN_SERVICE)
|
||||
.setName("PowerJob's built-in login system")
|
||||
;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateLoginUrl(LoginContext loginContext) {
|
||||
// 前端实现跳转,服务端返回特殊指令
|
||||
return "FE-REDIRECT:PowerJob";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThirdPartyUser login(LoginContext loginContext) {
|
||||
final String loginInfo = loginContext.getOriginParams();
|
||||
if (StringUtils.isEmpty(loginInfo)) {
|
||||
throw new IllegalArgumentException("can't find login Info");
|
||||
}
|
||||
|
||||
final Map<String, String> loginInfoMap = SJ.splitKvString(loginInfo);
|
||||
final String username = loginInfoMap.get(KEY_USERNAME);
|
||||
final String password = loginInfoMap.get(KEY_PASSWORD);
|
||||
|
||||
if (StringUtils.isAnyEmpty(username, password)) {
|
||||
Loggers.WEB.debug("[PowerJobLoginService] username or password is empty, login failed!");
|
||||
throw new IllegalArgumentException("username or password is empty!");
|
||||
}
|
||||
|
||||
final Optional<UserInfoDO> userInfoOpt = userInfoRepository.findByUsername(username);
|
||||
if (!userInfoOpt.isPresent()) {
|
||||
Loggers.WEB.debug("[PowerJobLoginService] can't find user by username: {}", username);
|
||||
throw new PowerJobException("can't find user by username: " + username);
|
||||
}
|
||||
|
||||
final UserInfoDO dbUser = userInfoOpt.get();
|
||||
|
||||
if (DigestUtils.rePassword(password, username).equals(dbUser.getPassword())) {
|
||||
ThirdPartyUser bizUser = new ThirdPartyUser();
|
||||
bizUser.setUsername(username);
|
||||
return bizUser;
|
||||
}
|
||||
|
||||
Loggers.WEB.debug("[PowerJobLoginService] user[{}]'s password is incorrect, login failed!", username);
|
||||
throw new PowerJobException("password is incorrect");
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package tech.powerjob.server.common;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* 统一定义日志
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/25
|
||||
*/
|
||||
public class Loggers {
|
||||
|
||||
/**
|
||||
* Web 层统一日志
|
||||
*/
|
||||
public static final Logger WEB = LoggerFactory.getLogger("P_SERVER_LOGGER_WEB");
|
||||
}
|
@ -3,6 +3,8 @@ package tech.powerjob.server.common;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Splitter;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Splitter & Joiner
|
||||
*
|
||||
@ -16,4 +18,9 @@ public class SJ {
|
||||
|
||||
public static final Joiner MONITOR_JOINER = Joiner.on("|").useForNull("-");
|
||||
|
||||
private static final Splitter.MapSplitter MAP_SPLITTER = Splitter.onPattern(";").withKeyValueSeparator(":");
|
||||
|
||||
public static Map<String, String> splitKvString(String kvString) {
|
||||
return MAP_SPLITTER.split(kvString);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
package tech.powerjob.server.common.utils;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
/**
|
||||
* 加密工具
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/25
|
||||
*/
|
||||
public class DigestUtils {
|
||||
|
||||
/**
|
||||
* 32位小写 md5
|
||||
* @param input 输入
|
||||
* @return md5
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static String md5(String input) {
|
||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||
md5.update(input.getBytes());
|
||||
byte[] byteArray = md5.digest();
|
||||
|
||||
BigInteger bigInt = new BigInteger(1, byteArray);
|
||||
// 参数16表示16进制
|
||||
StringBuilder result = new StringBuilder(bigInt.toString(16));
|
||||
// 不足32位高位补零
|
||||
while(result.length() < 32) {
|
||||
result.insert(0, "0");
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static String rePassword(String password, String salt) {
|
||||
String f1 = String.format("%s_%s_z", salt, password);
|
||||
return String.format("%s_%s_b", salt, md5(f1));
|
||||
}
|
||||
}
|
@ -26,6 +26,11 @@ public class UserInfoDO {
|
||||
private Long id;
|
||||
|
||||
private String username;
|
||||
/**
|
||||
* since 5.0.0
|
||||
* 昵称(第三方登陆的 username 很难识别,方便后续展示引入 nick)
|
||||
*/
|
||||
private String nick;
|
||||
|
||||
private String password;
|
||||
/**
|
||||
|
@ -0,0 +1,53 @@
|
||||
package tech.powerjob.server.persistence.remote.model;
|
||||
|
||||
import lombok.Data;
|
||||
import org.hibernate.annotations.GenericGenerator;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 用户角色表
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
@Data
|
||||
@Entity
|
||||
@Table(indexes = {
|
||||
@Index(name = "uidx01_user_id", columnList = "userId")
|
||||
})
|
||||
public class UserRoleDO {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
|
||||
@GenericGenerator(name = "native", strategy = "native")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 授予角色的用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 权限范围,namespace 还是 app
|
||||
*/
|
||||
private Integer scope;
|
||||
/**
|
||||
* 和 scope 一起组成授权目标,比如某个 app 或 某个 namespace
|
||||
*/
|
||||
private Long target;
|
||||
|
||||
/**
|
||||
* 角色,比如 Observer
|
||||
*/
|
||||
private Integer role;
|
||||
/**
|
||||
* 扩展字段
|
||||
*/
|
||||
private String extra;
|
||||
|
||||
private Date gmtCreate;
|
||||
|
||||
private Date gmtModified;
|
||||
}
|
@ -4,6 +4,7 @@ import tech.powerjob.server.persistence.remote.model.UserInfoDO;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 用户信息表数据库访问层
|
||||
@ -13,6 +14,8 @@ import java.util.List;
|
||||
*/
|
||||
public interface UserInfoRepository extends JpaRepository<UserInfoDO, Long> {
|
||||
|
||||
Optional<UserInfoDO> findByUsername(String username);
|
||||
|
||||
List<UserInfoDO> findByUsernameLike(String username);
|
||||
|
||||
List<UserInfoDO> findByIdIn(List<Long> userIds);
|
||||
|
@ -0,0 +1,19 @@
|
||||
package tech.powerjob.server.persistence.remote.repository;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import tech.powerjob.server.persistence.remote.model.UserRoleDO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* UserRoleRepository
|
||||
*
|
||||
* @author tjq
|
||||
* @since 2023/3/20
|
||||
*/
|
||||
public interface UserRoleRepository extends JpaRepository<UserRoleDO, Long> {
|
||||
|
||||
List<UserRoleDO> findAllByUserId(Long userId);
|
||||
|
||||
List<UserRoleDO> findAllByScopeAndTarget(Integer scope, Long target);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user