From 0caa8544091d976ad1fb2d3eedcb2ee5eeca36c9 Mon Sep 17 00:00:00 2001 From: tjq Date: Sun, 11 Feb 2024 10:14:47 +0800 Subject: [PATCH] feat: [auth] PowerJobLoginService --- powerjob-server/pom.xml | 5 + .../powerjob/server/auth/LoginUserHolder.java | 24 +++ .../powerjob/server/auth/PowerJobUser.java | 44 +++++ .../server/auth/common/AuthErrorCode.java | 29 ++++ .../auth/common/PowerJobAuthException.java | 27 +++ .../powerjob/server/auth/jwt/JwtService.java | 16 ++ .../server/auth/jwt/SecretProvider.java | 13 ++ .../auth/jwt/impl/DefaultSecretProvider.java | 18 ++ .../server/auth/jwt/impl/JwtServiceImpl.java | 92 ++++++++++ ...ntext.java => ThirdPartyLoginRequest.java} | 9 +- .../auth/login/ThirdPartyLoginService.java | 6 +- .../auth/login/impl/DingTalkLoginService.java | 12 +- ...va => PowerJobThirdPartyLoginService.java} | 11 +- .../auth/service/login/LoginRequest.java | 27 +++ .../service/login/PowerJobLoginService.java | 47 +++++ .../login/impl/PowerJobLoginServiceImpl.java | 160 ++++++++++++++++++ .../persistence/remote/model/UserInfoDO.java | 11 +- .../powerjob-server-starter/pom.xml | 4 + .../server/web/controller/AuthController.java | 26 +++ 19 files changed, 558 insertions(+), 23 deletions(-) create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/LoginUserHolder.java create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/PowerJobUser.java create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/common/AuthErrorCode.java create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/common/PowerJobAuthException.java create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/JwtService.java create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/SecretProvider.java create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/impl/DefaultSecretProvider.java create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/jwt/impl/JwtServiceImpl.java rename powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/{LoginContext.java => ThirdPartyLoginRequest.java} (72%) rename powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/login/impl/{PowerJobLoginService.java => PowerJobThirdPartyLoginService.java} (87%) create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/login/LoginRequest.java create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/login/PowerJobLoginService.java create mode 100644 powerjob-server/powerjob-server-auth/src/main/java/tech/powerjob/server/auth/service/login/impl/PowerJobLoginServiceImpl.java create mode 100644 powerjob-server/powerjob-server-starter/src/main/java/tech/powerjob/server/web/controller/AuthController.java 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; + } + +}