diff --git a/powerjob-server/pom.xml b/powerjob-server/pom.xml
index 8bb0ed3b..3b4ec5ea 100644
--- a/powerjob-server/pom.xml
+++ b/powerjob-server/pom.xml
@@ -97,6 +97,11 @@
powerjob-server-migrate
${project.version}
+
+ tech.powerjob
+ powerjob-server-auth
+ ${project.version}
+
tech.powerjob
powerjob-server-starter
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/LoginUserHolder.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/LoginUserHolder.java
new file mode 100644
index 00000000..1dd693c5
--- /dev/null
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/LoginUserHolder.java
@@ -0,0 +1,24 @@
+package tech.powerjob.server.auth;
+
+/**
+ * LoginUserHolder
+ *
+ * @author tjq
+ * @since 2023/4/16
+ */
+public class LoginUserHolder {
+
+ private static final ThreadLocal TL = new ThreadLocal<>();
+
+ public static PowerJobUser get() {
+ return TL.get();
+ }
+
+ public static void set(PowerJobUser powerJobUser) {
+ TL.set(powerJobUser);
+ }
+
+ public static void clean() {
+ TL.remove();
+ }
+}
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/PowerJobUser.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/PowerJobUser.java
new file mode 100644
index 00000000..c0a750c1
--- /dev/null
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/PowerJobUser.java
@@ -0,0 +1,44 @@
+package tech.powerjob.server.auth;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.io.Serializable;
+
+/**
+ * PowerJob 的 登陆用户
+ *
+ * @author tjq
+ * @since 2023/3/20
+ */
+@Getter
+@Setter
+@ToString
+public class PowerJobUser implements Serializable {
+
+ private Long id;
+
+ private String username;
+
+ /**
+ * 手机号
+ */
+ private String phone;
+ /**
+ * 邮箱地址
+ */
+ private String email;
+ /**
+ * webHook
+ */
+ private String webHook;
+ /**
+ * 扩展字段
+ */
+ private String extra;
+
+ /* ************** 以上为数据库字段 ************** */
+
+ private String jwtToken;
+}
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/common/AuthErrorCode.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/common/AuthErrorCode.java
new file mode 100644
index 00000000..25563819
--- /dev/null
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/common/AuthErrorCode.java
@@ -0,0 +1,29 @@
+package tech.powerjob.server.auth.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鉴权错误信息
+ *
+ * @author tjq
+ * @since 2024/2/11
+ */
+@Getter
+@AllArgsConstructor
+public enum AuthErrorCode {
+
+ USER_NOT_LOGIN("-100", "UserNotLoggedIn"),
+
+ NO_PERMISSION("-200", "NoPermission"),
+
+ /**
+ * 无效请求,一般是参数问题
+ */
+ INVALID_REQUEST("-300", "INVALID_REQUEST")
+
+ ;
+
+ private final String code;
+ private final String msg;
+}
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/common/PowerJobAuthException.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/common/PowerJobAuthException.java
new file mode 100644
index 00000000..adec56f4
--- /dev/null
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/common/PowerJobAuthException.java
@@ -0,0 +1,27 @@
+package tech.powerjob.server.auth.common;
+
+import lombok.Getter;
+
+/**
+ * 鉴权相关错误
+ *
+ * @author tjq
+ * @since 2024/2/10
+ */
+@Getter
+public class PowerJobAuthException extends RuntimeException {
+
+ private final String code;
+
+ private final String msg;
+
+ public PowerJobAuthException(AuthErrorCode errorCode) {
+ this.code = errorCode.getCode();
+ this.msg = errorCode.getMsg();
+ }
+
+ public PowerJobAuthException(AuthErrorCode errorCode, String extraMsg) {
+ this.code = errorCode.getCode();
+ this.msg = errorCode.getMsg().concat(":").concat(extraMsg);
+ }
+}
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/JwtService.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/JwtService.java
new file mode 100644
index 00000000..b6ea29a8
--- /dev/null
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/JwtService.java
@@ -0,0 +1,16 @@
+package tech.powerjob.server.auth.jwt;
+
+import java.util.Map;
+
+/**
+ * JWT 服务
+ *
+ * @author tjq
+ * @since 2023/3/20
+ */
+public interface JwtService {
+
+ String build(Map body, String extraSk);
+
+ Map parse(String jwt, String extraSk);
+}
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/SecretProvider.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/SecretProvider.java
new file mode 100644
index 00000000..349f393e
--- /dev/null
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/SecretProvider.java
@@ -0,0 +1,13 @@
+package tech.powerjob.server.auth.jwt;
+
+/**
+ * JWT 安全性的核心
+ * 对安全性有要求的接入方,可以自行重新该方法,自定义自己的安全 token 生成策略
+ *
+ * @author tjq
+ * @since 2023/3/20
+ */
+public interface SecretProvider {
+
+ String fetchSecretKey();
+}
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/impl/DefaultSecretProvider.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/impl/DefaultSecretProvider.java
new file mode 100644
index 00000000..0a26a557
--- /dev/null
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/impl/DefaultSecretProvider.java
@@ -0,0 +1,18 @@
+package tech.powerjob.server.auth.jwt.impl;
+
+import org.springframework.stereotype.Component;
+import tech.powerjob.server.auth.jwt.SecretProvider;
+
+/**
+ * PowerJob 默认实现
+ *
+ * @author tjq
+ * @since 2023/3/20
+ */
+@Component
+public class DefaultSecretProvider implements SecretProvider {
+ @Override
+ public String fetchSecretKey() {
+ return "ZQQZJ";
+ }
+}
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/impl/JwtServiceImpl.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/impl/JwtServiceImpl.java
new file mode 100644
index 00000000..15cb49bb
--- /dev/null
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/impl/JwtServiceImpl.java
@@ -0,0 +1,92 @@
+package tech.powerjob.server.auth.jwt.impl;
+
+import com.google.common.collect.Maps;
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.io.Decoders;
+import io.jsonwebtoken.security.Keys;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import tech.powerjob.server.auth.jwt.JwtService;
+import tech.powerjob.server.auth.jwt.SecretProvider;
+
+import javax.annotation.Resource;
+import java.security.Key;
+import java.util.Date;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * JWT 默认实现
+ *
+ * @author tjq
+ * @since 2023/3/20
+ */
+@Service
+public class JwtServiceImpl implements JwtService {
+
+ @Resource
+ private SecretProvider secretProvider;
+
+ /**
+ * JWT 客户端过期时间
+ */
+ @Value("${oms.auth.security.jwt.expire-seconds:604800}")
+ private int jwtExpireTime;
+
+ /**
+ * GoodSong
+ */
+ private static final String BASE_SECURITY =
+ "CengMengXiangZhangJianZouTianYa" +
+ "KanYiKanShiJieDeFanHua" +
+ "NianShaoDeXinZongYouXieQingKuang" +
+ "RuJinWoSiHaiWeiJia"
+ ;
+
+ @Override
+ public String build(Map body, String extraSk) {
+
+ final String secret = fetchSk(extraSk);
+ return innerBuild(secret, jwtExpireTime, body);
+ }
+
+ static String innerBuild(String secret, int expireSeconds, Map body) {
+ JwtBuilder jwtBuilder = Jwts.builder()
+ .setHeaderParam("typ", "JWT")
+ .addClaims(body)
+ .setSubject("PowerJob")
+ .setExpiration(new Date(System.currentTimeMillis() + 1000L * expireSeconds))
+ .setId(UUID.randomUUID().toString())
+ .signWith(genSecretKey(secret), SignatureAlgorithm.HS256);
+ return jwtBuilder.compact();
+ }
+
+ @Override
+ public Map parse(String jwt, String extraSk) {
+ return innerParse(fetchSk(extraSk), jwt);
+ }
+
+ private String fetchSk(String extraSk) {
+ if (StringUtils.isEmpty(extraSk)) {
+ return secretProvider.fetchSecretKey();
+ }
+ return secretProvider.fetchSecretKey().concat(extraSk);
+ }
+
+ static Map innerParse(String secret, String jwtStr) {
+ final Jws claimsJws = Jwts.parserBuilder()
+ .setSigningKey(genSecretKey(secret))
+ .build()
+ .parseClaimsJws(jwtStr);
+ Map ret = Maps.newHashMap();
+ ret.putAll(claimsJws.getBody());
+ return ret;
+ }
+
+ private static Key genSecretKey(String secret) {
+ byte[] keyBytes = Decoders.BASE64.decode(BASE_SECURITY.concat(secret));
+ return Keys.hmacShaKeyFor(keyBytes);
+ }
+
+}
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/LoginContext.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/ThirdPartyLoginRequest.java
similarity index 72%
rename from powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/LoginContext.java
rename to powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/ThirdPartyLoginRequest.java
index 16839dd5..52f74efc 100644
--- a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/LoginContext.java
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/ThirdPartyLoginRequest.java
@@ -1,18 +1,19 @@
package tech.powerjob.server.auth.login;
import lombok.Data;
+import lombok.experimental.Accessors;
import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
/**
- * 登录上下文
+ * 第三方登录请求
*
* @author tjq
* @since 2024/2/10
*/
@Data
-public class LoginContext {
+@Accessors(chain = true)
+public class ThirdPartyLoginRequest {
/**
* 原始参数,给第三方登录方式一个服务端和前端交互的数据通道。PowerJob 本身不感知其中的内容
@@ -20,6 +21,4 @@ public class LoginContext {
private String originParams;
private transient HttpServletRequest httpServletRequest;
-
- private transient HttpServletResponse httpServletResponse;
}
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/ThirdPartyLoginService.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/ThirdPartyLoginService.java
index 893eb851..87653b3f 100644
--- a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/ThirdPartyLoginService.java
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/ThirdPartyLoginService.java
@@ -1,5 +1,7 @@
package tech.powerjob.server.auth.login;
+import javax.servlet.http.HttpServletRequest;
+
/**
* 第三方登录服务
*
@@ -19,13 +21,13 @@ public interface ThirdPartyLoginService {
* @param loginContext 上下文
* @return 重定向地址
*/
- String generateLoginUrl(LoginContext loginContext);
+ String generateLoginUrl(HttpServletRequest httpServletRequest);
/**
* 执行第三方登录
* @param loginContext 上下文
* @return 登录地址
*/
- ThirdPartyUser login(LoginContext loginContext);
+ ThirdPartyUser login(ThirdPartyLoginRequest loginRequest);
}
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/impl/DingTalkLoginService.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/impl/DingTalkLoginService.java
index d3638917..731b20a5 100644
--- a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/impl/DingTalkLoginService.java
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/impl/DingTalkLoginService.java
@@ -10,12 +10,10 @@ 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.auth.login.*;
import tech.powerjob.server.common.Loggers;
+import javax.servlet.http.HttpServletRequest;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@@ -68,7 +66,7 @@ public class DingTalkLoginService implements ThirdPartyLoginService {
@Override
@SneakyThrows
- public String generateLoginUrl(LoginContext loginContext) {
+ public String generateLoginUrl(HttpServletRequest httpServletRequest) {
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!");
}
@@ -87,7 +85,7 @@ public class DingTalkLoginService implements ThirdPartyLoginService {
@Override
@SneakyThrows
- public ThirdPartyUser login(LoginContext loginContext) {
+ public ThirdPartyUser login(ThirdPartyLoginRequest loginRequest) {
try {
com.aliyun.dingtalkoauth2_1_0.Client client = authClient();
GetUserTokenRequest getUserTokenRequest = new GetUserTokenRequest()
@@ -95,7 +93,7 @@ public class DingTalkLoginService implements ThirdPartyLoginService {
.setClientId(dingTalkAppKey)
//应用基础信息-应用信息的AppSecret,,请务必替换为开发的应用AppSecret
.setClientSecret(dingTalkAppSecret)
- .setCode(loginContext.getHttpServletRequest().getParameter("authCode"))
+ .setCode(loginRequest.getHttpServletRequest().getParameter("authCode"))
.setGrantType("authorization_code");
GetUserTokenResponse getUserTokenResponse = client.getUserToken(getUserTokenRequest);
//获取用户个人 token
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/impl/PowerJobLoginService.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/impl/PowerJobThirdPartyLoginService.java
similarity index 87%
rename from powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/impl/PowerJobLoginService.java
rename to powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/impl/PowerJobThirdPartyLoginService.java
index 0b37a608..e669e1b6 100644
--- a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/impl/PowerJobLoginService.java
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/impl/PowerJobThirdPartyLoginService.java
@@ -3,8 +3,8 @@ 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.ThirdPartyLoginRequest;
import tech.powerjob.server.auth.login.ThirdPartyLoginService;
import tech.powerjob.server.auth.login.ThirdPartyUser;
import tech.powerjob.server.common.Loggers;
@@ -14,6 +14,7 @@ import tech.powerjob.server.persistence.remote.model.UserInfoDO;
import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Optional;
@@ -25,7 +26,7 @@ import java.util.Optional;
* @since 2023/3/20
*/
@Service
-public class PowerJobLoginService implements ThirdPartyLoginService {
+public class PowerJobThirdPartyLoginService implements ThirdPartyLoginService {
@Resource
private UserInfoRepository userInfoRepository;
@@ -44,14 +45,14 @@ public class PowerJobLoginService implements ThirdPartyLoginService {
}
@Override
- public String generateLoginUrl(LoginContext loginContext) {
+ public String generateLoginUrl(HttpServletRequest httpServletRequest) {
// 前端实现跳转,服务端返回特殊指令
return "FE-REDIRECT:PowerJob";
}
@Override
- public ThirdPartyUser login(LoginContext loginContext) {
- final String loginInfo = loginContext.getOriginParams();
+ public ThirdPartyUser login(ThirdPartyLoginRequest loginRequest) {
+ final String loginInfo = loginRequest.getOriginParams();
if (StringUtils.isEmpty(loginInfo)) {
throw new IllegalArgumentException("can't find login Info");
}
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/login/LoginRequest.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/login/LoginRequest.java
new file mode 100644
index 00000000..b20d86b0
--- /dev/null
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/login/LoginRequest.java
@@ -0,0 +1,27 @@
+package tech.powerjob.server.auth.service.login;
+
+import lombok.Data;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 执行登录的请求
+ *
+ * @author tjq
+ * @since 2024/2/10
+ */
+@Data
+public class LoginRequest {
+
+ /**
+ * 登录类型
+ */
+ private String loginType;
+
+ /**
+ * 原始参数,给第三方登录方式一个服务端和前端交互的数据通道。PowerJob 本身不感知其中的内容
+ */
+ private String originParams;
+
+ private transient HttpServletRequest httpServletRequest;
+}
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/login/PowerJobLoginService.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/login/PowerJobLoginService.java
new file mode 100644
index 00000000..0c570a6d
--- /dev/null
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/login/PowerJobLoginService.java
@@ -0,0 +1,47 @@
+package tech.powerjob.server.auth.service.login;
+
+import tech.powerjob.server.auth.PowerJobUser;
+import tech.powerjob.server.auth.common.PowerJobAuthException;
+import tech.powerjob.server.auth.login.LoginTypeInfo;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * PowerJob 登录服务
+ *
+ * @author tjq
+ * @since 2024/2/10
+ */
+public interface PowerJobLoginService {
+
+ /**
+ * 获取全部可登录的类型
+ * @return 全部可登录类型
+ */
+ List fetchSupportLoginTypes();
+
+
+ /**
+ * 获取第三方登录链接
+ * @param httpServletRequest http请求
+ * @return 重定向地址
+ */
+ String fetchThirdPartyLoginUrl(HttpServletRequest httpServletRequest);
+
+ /**
+ * 执行真正的登录请求,底层调用第三方登录服务完成登录
+ * @param loginRequest 登录请求
+ * @return 登录完成的 PowerJobUser
+ * @throws PowerJobAuthException 鉴权失败抛出异常
+ */
+ PowerJobUser doLogin(LoginRequest loginRequest) throws PowerJobAuthException;
+
+ /**
+ * 从 JWT 信息中解析用户登录信息
+ * @param httpServletRequest httpServletRequest
+ * @return PowerJob 用户
+ */
+ Optional ifLogin(HttpServletRequest httpServletRequest);
+}
diff --git a/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/login/impl/PowerJobLoginServiceImpl.java b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/login/impl/PowerJobLoginServiceImpl.java
new file mode 100644
index 00000000..9e9f074d
--- /dev/null
+++ b/powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/login/impl/PowerJobLoginServiceImpl.java
@@ -0,0 +1,160 @@
+package tech.powerjob.server.auth.service.login.impl;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import lombok.extern.slf4j.Slf4j;
+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.server.auth.PowerJobUser;
+import tech.powerjob.server.auth.common.AuthErrorCode;
+import tech.powerjob.server.auth.common.PowerJobAuthException;
+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.service.login.LoginRequest;
+import tech.powerjob.server.auth.service.login.PowerJobLoginService;
+import tech.powerjob.server.common.Loggers;
+import tech.powerjob.server.persistence.remote.model.UserInfoDO;
+import tech.powerjob.server.persistence.remote.repository.UserInfoRepository;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * PowerJob 登录服务
+ *
+ * @author tjq
+ * @since 2024/2/10
+ */
+@Slf4j
+@Service
+public class PowerJobLoginServiceImpl implements PowerJobLoginService {
+
+
+ private final JwtService jwtService;
+ private final UserInfoRepository userInfoRepository;
+ private final Map code2ThirdPartyLoginService;
+
+ private static final String JWT_NAME = "power_jwt";
+
+ private static final String KEY_USERNAME = "userName";
+
+ @Autowired
+ public PowerJobLoginServiceImpl(JwtService jwtService, UserInfoRepository userInfoRepository, List thirdPartyLoginServices) {
+
+ this.jwtService = jwtService;
+ this.userInfoRepository = userInfoRepository;
+
+ code2ThirdPartyLoginService = Maps.newHashMap();
+ thirdPartyLoginServices.forEach(s -> {
+ code2ThirdPartyLoginService.put(s.loginType().getType(), s);
+ log.info("[PowerJobLoginService] register ThirdPartyLoginService: {}", s.loginType());
+ });
+ }
+
+ @Override
+ public List fetchSupportLoginTypes() {
+ return Lists.newArrayList(code2ThirdPartyLoginService.values()).stream().map(ThirdPartyLoginService::loginType).collect(Collectors.toList());
+ }
+
+ @Override
+ public String fetchThirdPartyLoginUrl(HttpServletRequest httpServletRequest) {
+ return null;
+ }
+
+ @Override
+ public PowerJobUser doLogin(LoginRequest loginRequest) throws PowerJobAuthException {
+ final String loginType = loginRequest.getLoginType();
+ final ThirdPartyLoginService thirdPartyLoginService = fetchBizLoginService(loginType);
+
+ ThirdPartyLoginRequest thirdPartyLoginRequest = new ThirdPartyLoginRequest()
+ .setOriginParams(loginRequest.getOriginParams())
+ .setHttpServletRequest(loginRequest.getHttpServletRequest());
+
+ final ThirdPartyUser bizUser = thirdPartyLoginService.login(thirdPartyLoginRequest);
+
+ String dbUserName = String.format("%s_%s", loginType, bizUser.getUsername());
+ Optional powerJobUserOpt = userInfoRepository.findByUsername(dbUserName);
+
+ // 如果不存在用户,先同步创建用户
+ if (!powerJobUserOpt.isPresent()) {
+ UserInfoDO newUser = new UserInfoDO();
+ newUser.setUsername(dbUserName);
+ Loggers.WEB.info("[PowerJobLoginService] sync user to PowerJobUserSystem: {}", dbUserName);
+ userInfoRepository.saveAndFlush(newUser);
+
+ powerJobUserOpt = userInfoRepository.findByUsername(dbUserName);
+ }
+
+ PowerJobUser ret = new PowerJobUser();
+
+ // 理论上 100% 存在
+ if (powerJobUserOpt.isPresent()) {
+ final UserInfoDO dbUser = powerJobUserOpt.get();
+ BeanUtils.copyProperties(dbUser, ret);
+ ret.setUsername(dbUserName);
+ }
+
+ fillJwt(ret);
+
+ return ret;
+ }
+
+ @Override
+ public Optional ifLogin(HttpServletRequest httpServletRequest) {
+ final Optional userNameOpt = parseUserName(httpServletRequest);
+ return userNameOpt.flatMap(uname -> userInfoRepository.findByUsername(uname).map(userInfoDO -> {
+ PowerJobUser powerJobUser = new PowerJobUser();
+ BeanUtils.copyProperties(userInfoDO, powerJobUser);
+ return powerJobUser;
+ }));
+ }
+
+ private ThirdPartyLoginService fetchBizLoginService(String loginType) {
+ final ThirdPartyLoginService loginService = code2ThirdPartyLoginService.get(loginType);
+ if (loginService == null) {
+ throw new PowerJobAuthException(AuthErrorCode.INVALID_REQUEST, "can't find ThirdPartyLoginService by type: " + loginType);
+ }
+ return loginService;
+ }
+
+ private void fillJwt(PowerJobUser powerJobUser) {
+ Map jwtMap = Maps.newHashMap();
+
+ // 不能下发 userId,容易被轮询爆破
+ jwtMap.put(KEY_USERNAME, powerJobUser.getUsername());
+
+ powerJobUser.setJwtToken(jwtService.build(jwtMap, null));
+ }
+
+ private Optional parseUserName(HttpServletRequest httpServletRequest) {
+ // header、cookie 都能获取
+ String jwtStr = httpServletRequest.getHeader(JWT_NAME);
+ if (StringUtils.isEmpty(jwtStr)) {
+ for (Cookie cookie : httpServletRequest.getCookies()) {
+ if (cookie.getName().equals(JWT_NAME)) {
+ jwtStr = cookie.getValue();
+ }
+ }
+ }
+ if (StringUtils.isEmpty(jwtStr)) {
+ return Optional.empty();
+ }
+ final Map jwtBodyMap = jwtService.parse(jwtStr, null);
+ final Object userName = jwtBodyMap.get(KEY_USERNAME);
+
+ if (userName == null) {
+ return Optional.empty();
+ }
+
+ return Optional.of(String.valueOf(userName));
+ }
+}
diff --git a/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/UserInfoDO.java b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/UserInfoDO.java
index e13e5724..21df9364 100644
--- a/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/UserInfoDO.java
+++ b/powerjob-server/powerjob-server-persistence/src/main/java/tech/powerjob/server/persistence/remote/model/UserInfoDO.java
@@ -8,16 +8,19 @@ import java.util.Date;
/**
* 用户信息表
+ * 5.0.0 可能不兼容改动:为了支持第三方登录,需要通过 username 与第三方登录系统做匹配,该列需要声明为唯一索引,确保全局唯一
*
* @author tjq
* @since 2020/4/12
*/
@Data
@Entity
-@Table(indexes = {
- @Index(name = "uidx01_user_info", columnList = "username"),
- @Index(name = "uidx02_user_info", columnList = "email")
-})
+@Table(uniqueConstraints = {
+ @UniqueConstraint(name = "uidx01_user_name", columnNames = {"username"})
+},
+ indexes = {
+ @Index(name = "uidx02_user_info", columnList = "email")
+ })
public class UserInfoDO {
@Id
diff --git a/powerjob-server/powerjob-server-starter/pom.xml b/powerjob-server/powerjob-server-starter/pom.xml
index 9ac1e3cf..c4900ac6 100644
--- a/powerjob-server/powerjob-server-starter/pom.xml
+++ b/powerjob-server/powerjob-server-starter/pom.xml
@@ -43,6 +43,10 @@
tech.powerjob
powerjob-server-core
+
+ tech.powerjob
+ powerjob-server-auth
+
tech.powerjob
powerjob-server-migrate
diff --git a/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/AuthController.java b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/AuthController.java
new file mode 100644
index 00000000..1ec4bd05
--- /dev/null
+++ b/powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/AuthController.java
@@ -0,0 +1,26 @@
+package tech.powerjob.server.web.controller;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import tech.powerjob.common.response.ResultDTO;
+import tech.powerjob.server.auth.login.LoginTypeInfo;
+
+import java.util.List;
+
+/**
+ * 登录 & 权限相关
+ *
+ * @author tjq
+ * @since 2023/4/16
+ */
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+ @GetMapping("/listSupportLoginTypes")
+ public ResultDTO> listSupportLoginTypes() {
+ return null;
+ }
+
+}