diff --git a/config/nacos/application-common.yml b/config/nacos/application-common.yml
index 6d2110f4e..d66321c41 100644
--- a/config/nacos/application-common.yml
+++ b/config/nacos/application-common.yml
@@ -192,6 +192,16 @@ mybatis-encryptor:
publicKey:
privateKey:
+# api接口加密
+api-decrypt:
+ # 是否开启全局接口加密
+ enabled: true
+ # AES 加密头标识
+ headerFlag: encrypt-key
+ # 公私钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换
+ publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
+ privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y=
+
# 接口文档配置
springdoc:
api-docs:
diff --git a/config/nacos/ruoyi-auth.yml b/config/nacos/ruoyi-auth.yml
index 9285a23a3..f614ce72b 100644
--- a/config/nacos/ruoyi-auth.yml
+++ b/config/nacos/ruoyi-auth.yml
@@ -1,3 +1,18 @@
+# 安全配置
+security:
+ # 验证码
+ captcha:
+ # 是否开启验证码
+ enabled: true
+ # 验证码类型 math 数组计算 char 字符验证
+ type: MATH
+ # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
+ category: CIRCLE
+ # 数字验证码位数
+ numberLength: 1
+ # 字符验证码长度
+ charLength: 4
+
# 用户配置
user:
password:
@@ -5,3 +20,73 @@ user:
maxRetryCount: 5
# 密码锁定时间(默认10分钟)
lockTime: 10
+
+--- # 三方授权
+justauth:
+ enabled: true
+ # 前端外网访问地址
+ address: http://localhost:80
+ type:
+ maxkey:
+ # maxkey 服务器地址
+ # 注意 如下均配置均不需要修改 maxkey 已经内置好了数据
+ server-url: http://sso.maxkey.top
+ client-id: 876892492581044224
+ client-secret: x1Y5MTMwNzIwMjMxNTM4NDc3Mzche8
+ redirect-uri: ${justauth.address}/social-callback?source=maxkey
+ qq:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=qq
+ union-id: false
+ weibo:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=weibo
+ gitee:
+ client-id: 91436b7940090d09c72c7daf85b959cfd5f215d67eea73acbf61b6b590751a98
+ client-secret: 02c6fcfd70342980cd8dd2f2c06c1a350645d76c754d7a264c4e125f9ba915ac
+ redirect-uri: ${justauth.address}/social-callback?source=gitee
+ dingtalk:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=dingtalk
+ baidu:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=baidu
+ csdn:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=csdn
+ coding:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=coding
+ coding-group-name: xx
+ oschina:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=oschina
+ alipay:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=alipay
+ alipay-public-key: MIIB**************DAQAB
+ wechat_open:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=wechat_open
+ wechat_mp:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=wechat_mp
+ wechat_enterprise:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=wechat_enterprise
+ agent-id: 1000002
+ gitlab:
+ client-id: 10**********6
+ client-secret: 1f7d08**********5b7**********29e
+ redirect-uri: ${justauth.address}/social-callback?source=gitlab
diff --git a/config/nacos/ruoyi-gateway.yml b/config/nacos/ruoyi-gateway.yml
index f87da2b59..70489885e 100644
--- a/config/nacos/ruoyi-gateway.yml
+++ b/config/nacos/ruoyi-gateway.yml
@@ -1,17 +1,5 @@
# 安全配置
security:
- # 验证码
- captcha:
- # 是否开启验证码
- enabled: true
- # 验证码类型 math 数组计算 char 字符验证
- type: MATH
- # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
- category: CIRCLE
- # 数字验证码位数
- numberLength: 1
- # 字符验证码长度
- charLength: 4
# 防止XSS攻击
xss:
enabled: true
@@ -49,8 +37,6 @@ spring:
predicates:
- Path=/auth/**
filters:
- # 验证码处理
- - ValidateCodeFilter
- StripPrefix=1
# 代码生成
- id: ruoyi-gen
diff --git a/pom.xml b/pom.xml
index f94ae48bf..cb2739c3e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,6 +46,7 @@
2.14.2
1.3.1
0.2.0
+ 1.16.5
2.7.0
@@ -135,6 +136,13 @@
import
+
+
+ me.zhyd.oauth
+ JustAuth
+ ${justauth.version}
+
+
org.dromara
diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteClientService.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteClientService.java
new file mode 100644
index 000000000..89c30687a
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteClientService.java
@@ -0,0 +1,20 @@
+package org.dromara.system.api;
+
+import org.dromara.system.api.domain.vo.RemoteClientVo;
+
+/**
+ * 客户端服务
+ *
+ * @author Michelle.Chung
+ */
+public interface RemoteClientService {
+
+ /**
+ * 根据客户端id获取客户端详情
+ *
+ * @param clientId 客户端id
+ * @return 客户端对象
+ */
+ RemoteClientVo queryByClientId(String clientId);
+
+}
diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteSocialService.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteSocialService.java
new file mode 100644
index 000000000..632d92f11
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteSocialService.java
@@ -0,0 +1,28 @@
+package org.dromara.system.api;
+
+import org.dromara.system.api.domain.bo.RemoteSocialBo;
+import org.dromara.system.api.domain.vo.RemoteSocialVo;
+
+/**
+ * 社会化关系服务
+ *
+ * @author Michelle.Chung
+ */
+public interface RemoteSocialService {
+
+ /**
+ * 根据 authId 查询用户信息
+ */
+ RemoteSocialVo selectByAuthId(String authId);
+
+ /**
+ * 保存社会化关系
+ */
+ void insertByBo(RemoteSocialBo bo);
+
+ /**
+ * 删除社会化关系
+ */
+ Boolean deleteWithValidById(Long socialId);
+
+}
diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteUserService.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteUserService.java
index ec401d54c..a3025f576 100644
--- a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteUserService.java
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteUserService.java
@@ -62,4 +62,6 @@ public interface RemoteUserService {
* @return 结果
*/
String selectUserNameById(Long userId);
+
+ void recordLoginInfo(Long userId);
}
diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/bo/RemoteSocialBo.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/bo/RemoteSocialBo.java
new file mode 100644
index 000000000..f2b6549dc
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/bo/RemoteSocialBo.java
@@ -0,0 +1,123 @@
+package org.dromara.system.api.domain.bo;
+
+import lombok.Data;
+
+/**
+ * 社会化关系业务对象 sys_social
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class RemoteSocialBo {
+
+ /**
+ * 主键
+ */
+ private Long id;
+
+ /**
+ * 的唯一ID
+ */
+ private String authId;
+
+ /**
+ * 用户来源
+ */
+ private String source;
+
+ /**
+ * 用户的授权令牌
+ */
+ private String accessToken;
+
+ /**
+ * 用户的授权令牌的有效期,部分平台可能没有
+ */
+ private int expireIn;
+
+ /**
+ * 刷新令牌,部分平台可能没有
+ */
+ private String refreshToken;
+
+ /**
+ * 平台唯一id
+ */
+ private String openId;
+
+ /**
+ * 用户的 ID
+ */
+ private Long userId;
+
+ /**
+ * 平台的授权信息,部分平台可能没有
+ */
+ private String accessCode;
+
+ /**
+ * 用户的 unionid
+ */
+ private String unionId;
+
+ /**
+ * 授予的权限,部分平台可能没有
+ */
+ private String scope;
+
+ /**
+ * 授权的第三方账号
+ */
+ private String userName;
+
+ /**
+ * 授权的第三方昵称
+ */
+ private String nickName;
+
+ /**
+ * 授权的第三方邮箱
+ */
+ private String email;
+
+ /**
+ * 授权的第三方头像地址
+ */
+ private String avatar;
+
+ /**
+ * 个别平台的授权信息,部分平台可能没有
+ */
+ private String tokenType;
+
+ /**
+ * id token,部分平台可能没有
+ */
+ private String idToken;
+
+ /**
+ * 小米平台用户的附带属性,部分平台可能没有
+ */
+ private String macAlgorithm;
+
+ /**
+ * 小米平台用户的附带属性,部分平台可能没有
+ */
+ private String macKey;
+
+ /**
+ * 用户的授权code,部分平台可能没有
+ */
+ private String code;
+
+ /**
+ * Twitter平台用户的附带属性,部分平台可能没有
+ */
+ private String oauthToken;
+
+ /**
+ * Twitter平台用户的附带属性,部分平台可能没有
+ */
+ private String oauthTokenSecret;
+
+}
diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteClientVo.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteClientVo.java
new file mode 100644
index 000000000..400f56b74
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteClientVo.java
@@ -0,0 +1,71 @@
+package org.dromara.system.api.domain.vo;
+
+import lombok.Data;
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.List;
+
+
+/**
+ * 授权管理视图对象 sys_client
+ *
+ * @author Michelle.Chung
+ */
+@Data
+public class RemoteClientVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * id
+ */
+ private Long id;
+
+ /**
+ * 客户端id
+ */
+ private String clientId;
+
+ /**
+ * 客户端key
+ */
+ private String clientKey;
+
+ /**
+ * 客户端秘钥
+ */
+ private String clientSecret;
+
+ /**
+ * 授权类型
+ */
+ private List grantTypeList;
+
+ /**
+ * 授权类型
+ */
+ private String grantType;
+
+ /**
+ * 设备类型
+ */
+ private String deviceType;
+
+ /**
+ * token活跃超时时间
+ */
+ private Long activeTimeout;
+
+ /**
+ * token固定超时时间
+ */
+ private Long timeout;
+
+ /**
+ * 状态(0正常 1停用)
+ */
+ private String status;
+
+
+}
diff --git a/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteSocialVo.java b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteSocialVo.java
new file mode 100644
index 000000000..71e8d58a4
--- /dev/null
+++ b/ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteSocialVo.java
@@ -0,0 +1,135 @@
+package org.dromara.system.api.domain.vo;
+
+import lombok.Data;
+import java.io.Serial;
+import java.io.Serializable;
+
+
+/**
+ * 社会化关系视图对象 sys_social
+ *
+ * @author thiszhc
+ */
+@Data
+public class RemoteSocialVo implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键
+ */
+ private Long id;
+
+ /**
+ * 用户ID
+ */
+ private Long userId;
+
+ /**
+ * 租户ID
+ */
+ private String tenantId;
+
+ /**
+ * 的唯一ID
+ */
+ private String authId;
+
+ /**
+ * 用户来源
+ */
+ private String source;
+
+ /**
+ * 用户的授权令牌
+ */
+ private String accessToken;
+
+ /**
+ * 用户的授权令牌的有效期,部分平台可能没有
+ */
+ private int expireIn;
+
+ /**
+ * 刷新令牌,部分平台可能没有
+ */
+ private String refreshToken;
+
+ /**
+ * 用户的 open id
+ */
+ private String openId;
+
+ /**
+ * 授权的第三方账号
+ */
+ private String userName;
+
+ /**
+ * 授权的第三方昵称
+ */
+ private String nickName;
+
+ /**
+ * 授权的第三方邮箱
+ */
+ private String email;
+
+ /**
+ * 授权的第三方头像地址
+ */
+ private String avatar;
+
+
+ /**
+ * 平台的授权信息,部分平台可能没有
+ */
+ private String accessCode;
+
+ /**
+ * 用户的 unionid
+ */
+ private String unionId;
+
+ /**
+ * 授予的权限,部分平台可能没有
+ */
+ private String scope;
+
+ /**
+ * 个别平台的授权信息,部分平台可能没有
+ */
+ private String tokenType;
+
+ /**
+ * id token,部分平台可能没有
+ */
+ private String idToken;
+
+ /**
+ * 小米平台用户的附带属性,部分平台可能没有
+ */
+ private String macAlgorithm;
+
+ /**
+ * 小米平台用户的附带属性,部分平台可能没有
+ */
+ private String macKey;
+
+ /**
+ * 用户的授权code,部分平台可能没有
+ */
+ private String code;
+
+ /**
+ * Twitter平台用户的附带属性,部分平台可能没有
+ */
+ private String oauthToken;
+
+ /**
+ * Twitter平台用户的附带属性,部分平台可能没有
+ */
+ private String oauthTokenSecret;
+
+}
diff --git a/ruoyi-auth/pom.xml b/ruoyi-auth/pom.xml
index fc312a10a..6694c4b07 100644
--- a/ruoyi-auth/pom.xml
+++ b/ruoyi-auth/pom.xml
@@ -28,6 +28,11 @@
spring-cloud-starter-alibaba-nacos-config
+
+ cn.hutool
+ hutool-captcha
+
+
org.dromara
ruoyi-common-sentinel
@@ -39,6 +44,11 @@
ruoyi-common-security
+
+ org.dromara
+ ruoyi-common-social
+
+
org.dromara
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/captcha/UnsignedMathGenerator.java b/ruoyi-auth/src/main/java/org/dromara/auth/captcha/UnsignedMathGenerator.java
similarity index 98%
rename from ruoyi-gateway/src/main/java/org/dromara/gateway/captcha/UnsignedMathGenerator.java
rename to ruoyi-auth/src/main/java/org/dromara/auth/captcha/UnsignedMathGenerator.java
index 92c5ee205..feb4cdf43 100644
--- a/ruoyi-gateway/src/main/java/org/dromara/gateway/captcha/UnsignedMathGenerator.java
+++ b/ruoyi-auth/src/main/java/org/dromara/auth/captcha/UnsignedMathGenerator.java
@@ -1,4 +1,4 @@
-package org.dromara.gateway.captcha;
+package org.dromara.auth.captcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.math.Calculator;
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/CaptchaConfig.java b/ruoyi-auth/src/main/java/org/dromara/auth/config/CaptchaConfig.java
similarity index 97%
rename from ruoyi-gateway/src/main/java/org/dromara/gateway/config/CaptchaConfig.java
rename to ruoyi-auth/src/main/java/org/dromara/auth/config/CaptchaConfig.java
index 94d87db45..588001675 100644
--- a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/CaptchaConfig.java
+++ b/ruoyi-auth/src/main/java/org/dromara/auth/config/CaptchaConfig.java
@@ -1,4 +1,4 @@
-package org.dromara.gateway.config;
+package org.dromara.auth.config;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/service/impl/ValidateCodeServiceImpl.java b/ruoyi-auth/src/main/java/org/dromara/auth/controller/CaptchaController.java
similarity index 52%
rename from ruoyi-gateway/src/main/java/org/dromara/gateway/service/impl/ValidateCodeServiceImpl.java
rename to ruoyi-auth/src/main/java/org/dromara/auth/controller/CaptchaController.java
index 7dca913fc..9a3815530 100644
--- a/ruoyi-gateway/src/main/java/org/dromara/gateway/service/impl/ValidateCodeServiceImpl.java
+++ b/ruoyi-auth/src/main/java/org/dromara/auth/controller/CaptchaController.java
@@ -1,53 +1,55 @@
-package org.dromara.gateway.service.impl;
+package org.dromara.auth.controller;
+import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.auth.domain.vo.CaptchaVo;
+import org.dromara.auth.enums.CaptchaType;
+import org.dromara.auth.properties.CaptchaProperties;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.R;
-import org.dromara.common.core.exception.CaptchaException;
-import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.redis.utils.RedisUtils;
-import org.dromara.gateway.config.properties.CaptchaProperties;
-import org.dromara.gateway.enums.CaptchaType;
-import org.dromara.gateway.service.ValidateCodeService;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
-import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
-import java.io.IOException;
import java.time.Duration;
-import java.util.HashMap;
-import java.util.Map;
/**
- * 验证码实现处理
+ * 验证码操作处理
*
- * @author ruoyi
+ * @author Lion Li
*/
-@Service
-public class ValidateCodeServiceImpl implements ValidateCodeService {
- @Autowired
- private CaptchaProperties captchaProperties;
+@SaIgnore
+@Slf4j
+@Validated
+@RequiredArgsConstructor
+@RestController
+public class CaptchaController {
+
+ private final CaptchaProperties captchaProperties;
/**
* 生成验证码
*/
- @Override
- public R
+
+ org.dromara
+ ruoyi-common-social
+ ${revision}
+
+
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java
index 38593a33d..8e5a6eb98 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/GlobalConstants.java
@@ -31,4 +31,10 @@ public interface GlobalConstants {
* 登录账户密码错误次数 redis key
*/
String PWD_ERR_CNT_KEY = GLOBAL_REDIS_KEY + "pwd_err_cnt:";
+
+ /**
+ * 三方认证 redis key
+ */
+ String SOCIAL_AUTH_CODE_KEY = GLOBAL_REDIS_KEY + "social_auth_codes:";
+
}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java
new file mode 100644
index 000000000..33d092dcb
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/model/LoginBody.java
@@ -0,0 +1,122 @@
+package org.dromara.common.core.domain.model;
+
+import jakarta.validation.constraints.Email;
+import org.dromara.common.core.constant.UserConstants;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.dromara.common.core.validate.auth.*;
+import org.hibernate.validator.constraints.Length;
+
+import jakarta.validation.constraints.NotBlank;
+
+/**
+ * 用户登录对象
+ *
+ * @author Lion Li
+ */
+@Data
+@NoArgsConstructor
+public class LoginBody {
+
+ /**
+ * 客户端id
+ */
+ @NotBlank(message = "{auth.clientid.not.blank}")
+ private String clientId;
+
+ /**
+ * 客户端key
+ */
+ private String clientKey;
+
+ /**
+ * 客户端秘钥
+ */
+ private String clientSecret;
+
+ /**
+ * 授权类型
+ */
+ @NotBlank(message = "{auth.grant.type.not.blank}")
+ private String grantType;
+
+ /**
+ * 租户ID
+ */
+ @NotBlank(message = "{tenant.number.not.blank}")
+ private String tenantId;
+
+ /**
+ * 用户名
+ */
+ @NotBlank(message = "{user.username.not.blank}", groups = {PasswordGroup.class})
+ @Length(min = UserConstants.USERNAME_MIN_LENGTH, max = UserConstants.USERNAME_MAX_LENGTH, message = "{user.username.length.valid}", groups = {PasswordGroup.class})
+ private String username;
+
+ /**
+ * 用户密码
+ */
+ @NotBlank(message = "{user.password.not.blank}", groups = {PasswordGroup.class})
+ @Length(min = UserConstants.PASSWORD_MIN_LENGTH, max = UserConstants.PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}", groups = {PasswordGroup.class})
+ private String password;
+
+ /**
+ * 验证码
+ */
+ private String code;
+
+ /**
+ * 唯一标识
+ */
+ private String uuid;
+
+ /**
+ * 手机号
+ */
+ @NotBlank(message = "{user.phonenumber.not.blank}", groups = {SmsGroup.class})
+ private String phonenumber;
+
+ /**
+ * 短信code
+ */
+ @NotBlank(message = "{sms.code.not.blank}", groups = {SmsGroup.class})
+ private String smsCode;
+
+ /**
+ * 邮箱
+ */
+ @NotBlank(message = "{user.email.not.blank}", groups = {EmailGroup.class})
+ @Email(message = "{user.email.not.valid}")
+ private String email;
+
+ /**
+ * 邮箱code
+ */
+ @NotBlank(message = "{email.code.not.blank}", groups = {EmailGroup.class})
+ private String emailCode;
+
+ /**
+ * 小程序code
+ */
+ @NotBlank(message = "{xcx.code.not.blank}", groups = {WechatGroup.class})
+ private String xcxCode;
+
+ /**
+ * 第三方登录平台
+ */
+ @NotBlank(message = "{social.source.not.blank}" , groups = {SocialGroup.class})
+ private String source;
+
+ /**
+ * 第三方登录code
+ */
+ @NotBlank(message = "{social.code.not.blank}" , groups = {SocialGroup.class})
+ private String socialCode;
+
+ /**
+ * 第三方登录socialState
+ */
+ @NotBlank(message = "{social.state.not.blank}" , groups = {SocialGroup.class})
+ private String socialState;
+
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/EmailGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/EmailGroup.java
new file mode 100644
index 000000000..130932f4a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/EmailGroup.java
@@ -0,0 +1,7 @@
+package org.dromara.common.core.validate.auth;
+
+/**
+ * @Author Michelle.Chung
+ */
+public interface EmailGroup {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/PasswordGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/PasswordGroup.java
new file mode 100644
index 000000000..1516172ad
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/PasswordGroup.java
@@ -0,0 +1,7 @@
+package org.dromara.common.core.validate.auth;
+
+/**
+ * @Author Michelle.Chung
+ */
+public interface PasswordGroup {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/SmsGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/SmsGroup.java
new file mode 100644
index 000000000..d56286027
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/SmsGroup.java
@@ -0,0 +1,7 @@
+package org.dromara.common.core.validate.auth;
+
+/**
+ * @Author Michelle.Chung
+ */
+public interface SmsGroup {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/SocialGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/SocialGroup.java
new file mode 100644
index 000000000..2b19ffe30
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/SocialGroup.java
@@ -0,0 +1,4 @@
+package org.dromara.common.core.validate.auth;
+
+public interface SocialGroup {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/WechatGroup.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/WechatGroup.java
new file mode 100644
index 000000000..b397f1870
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/validate/auth/WechatGroup.java
@@ -0,0 +1,7 @@
+package org.dromara.common.core.validate.auth;
+
+/**
+ * @Author Michelle.Chung
+ */
+public interface WechatGroup {
+}
diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties
index 4f2da28b4..5c9f42271 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties
+++ b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages.properties
@@ -28,6 +28,9 @@ user.register.error=注册失败,请联系系统管理人员
user.notfound=请重新登录
user.forcelogout=管理员强制退出,请重新登录
user.unknown.error=未知错误,请重新登录
+auth.grant.type.error=认证权限类型错误
+auth.grant.type.not.blank=认证权限类型不能为空
+auth.clientid.not.blank=认证客户端id不能为空
##文件上传消息
upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB!
upload.filename.exceed.length=上传的文件名最长{0}个字符
diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties
index 0c738a37e..2ab82a667 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties
+++ b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_en_US.properties
@@ -28,6 +28,9 @@ user.register.error=Register failed, please contact system administrator
user.notfound=Please login again
user.forcelogout=The administrator is forced to exit,please login again
user.unknown.error=Unknown error, please login again
+auth.grant.type.error=Auth grant type error
+auth.grant.type.not.blank=Auth grant type cannot be blank
+auth.clientid.not.blank=Auth clientid cannot be blank
##文件上传消息
upload.exceed.maxSize=The uploaded file size exceeds the limit file size!
the maximum allowed file size is:{0}MB!
upload.filename.exceed.length=The maximum length of uploaded file name is {0} characters
diff --git a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties
index 4f2da28b4..5c9f42271 100644
--- a/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties
+++ b/ruoyi-common/ruoyi-common-core/src/main/resources/i18n/messages_zh_CN.properties
@@ -28,6 +28,9 @@ user.register.error=注册失败,请联系系统管理人员
user.notfound=请重新登录
user.forcelogout=管理员强制退出,请重新登录
user.unknown.error=未知错误,请重新登录
+auth.grant.type.error=认证权限类型错误
+auth.grant.type.not.blank=认证权限类型不能为空
+auth.clientid.not.blank=认证客户端id不能为空
##文件上传消息
upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB!
upload.filename.exceed.length=上传的文件名最长{0}个字符
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java
new file mode 100644
index 000000000..098f6bc8d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/config/ApiDecryptAutoConfiguration.java
@@ -0,0 +1,32 @@
+package org.dromara.common.encrypt.config;
+
+import jakarta.servlet.DispatcherType;
+import org.dromara.common.encrypt.filter.CryptoFilter;
+import org.dromara.common.encrypt.properties.ApiDecryptProperties;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * api 解密自动配置
+ *
+ * @author wdhcr
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(ApiDecryptProperties.class)
+@ConditionalOnProperty(value = "api-decrypt.enabled", havingValue = "true")
+public class ApiDecryptAutoConfiguration {
+
+ @Bean
+ public FilterRegistrationBean cryptoFilterRegistration(ApiDecryptProperties properties) {
+ FilterRegistrationBean registration = new FilterRegistrationBean<>();
+ registration.setDispatcherTypes(DispatcherType.REQUEST);
+ registration.setFilter(new CryptoFilter(properties));
+ registration.addUrlPatterns("/*");
+ registration.setName("cryptoFilter");
+ registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE);
+ return registration;
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java
new file mode 100644
index 000000000..c31e025ac
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/CryptoFilter.java
@@ -0,0 +1,49 @@
+package org.dromara.common.encrypt.filter;
+
+import cn.hutool.core.util.ObjectUtil;
+import jakarta.servlet.*;
+import jakarta.servlet.http.HttpServletRequest;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.encrypt.properties.ApiDecryptProperties;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+
+import java.io.IOException;
+
+
+/**
+ * Crypto 过滤器
+ *
+ * @author wdhcr
+ */
+public class CryptoFilter implements Filter {
+ private final ApiDecryptProperties properties;
+
+ public CryptoFilter(ApiDecryptProperties properties) {
+ this.properties = properties;
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ ServletRequest requestWrapper = null;
+ HttpServletRequest servletRequest = (HttpServletRequest) request;
+ // 是否为 json 请求
+ if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) {
+ // 是否为 put 或者 post 请求
+ if (HttpMethod.PUT.matches(servletRequest.getMethod()) || HttpMethod.POST.matches(servletRequest.getMethod())) {
+ // 是否存在加密标头
+ String headerValue = servletRequest.getHeader(properties.getHeaderFlag());
+ if (StringUtils.isNotBlank(headerValue)) {
+ requestWrapper = new DecryptRequestBodyWrapper(servletRequest, properties.getPublicKey(), properties.getPrivateKey(), properties.getHeaderFlag());
+ }
+ }
+ }
+
+ chain.doFilter(ObjectUtil.defaultIfNull(requestWrapper, request), response);
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java
new file mode 100644
index 000000000..fa9a31071
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java
@@ -0,0 +1,94 @@
+package org.dromara.common.encrypt.filter;
+
+import cn.hutool.core.io.IoUtil;
+import jakarta.servlet.ReadListener;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import org.dromara.common.core.constant.Constants;
+import org.dromara.common.encrypt.utils.EncryptUtils;
+import org.springframework.http.MediaType;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 解密请求参数工具类
+ *
+ * @author wdhcr
+ */
+public class DecryptRequestBodyWrapper extends HttpServletRequestWrapper {
+
+ private final byte[] body;
+
+ public DecryptRequestBodyWrapper(HttpServletRequest request, String publicKey, String privateKey, String headerFlag) throws IOException {
+ super(request);
+ // 获取 AES 密码 采用 RSA 加密
+ String headerRsa = request.getHeader(headerFlag);
+ String decryptAes = EncryptUtils.decryptByRsa(headerRsa, privateKey);
+ // 解密 AES 密码
+ String aesPassword = EncryptUtils.decryptByBase64(decryptAes);
+ request.setCharacterEncoding(Constants.UTF8);
+ byte[] readBytes = IoUtil.readBytes(request.getInputStream(), false);
+ String requestBody = new String(readBytes, StandardCharsets.UTF_8);
+ // 解密 body 采用 AES 加密
+ String decryptBody = EncryptUtils.decryptByAes(requestBody, aesPassword);
+ body = decryptBody.getBytes(StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public BufferedReader getReader() {
+ return new BufferedReader(new InputStreamReader(getInputStream()));
+ }
+
+
+ @Override
+ public int getContentLength() {
+ return body.length;
+ }
+
+ @Override
+ public long getContentLengthLong() {
+ return body.length;
+ }
+
+ @Override
+ public String getContentType() {
+ return MediaType.APPLICATION_JSON_VALUE;
+ }
+
+
+ @Override
+ public ServletInputStream getInputStream() {
+ final ByteArrayInputStream bais = new ByteArrayInputStream(body);
+ return new ServletInputStream() {
+ @Override
+ public int read() {
+ return bais.read();
+ }
+
+ @Override
+ public int available() {
+ return body.length;
+ }
+
+ @Override
+ public boolean isFinished() {
+ return false;
+ }
+
+ @Override
+ public boolean isReady() {
+ return false;
+ }
+
+ @Override
+ public void setReadListener(ReadListener readListener) {
+
+ }
+ };
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java
new file mode 100644
index 000000000..9e25b7b9a
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/properties/ApiDecryptProperties.java
@@ -0,0 +1,34 @@
+package org.dromara.common.encrypt.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * api解密属性配置类
+ * @author wdhcr
+ */
+@Data
+@ConfigurationProperties(prefix = "api-decrypt")
+public class ApiDecryptProperties {
+
+ /**
+ * 加密开关
+ */
+ private Boolean enabled;
+
+ /**
+ * 头部标识
+ */
+ private String headerFlag;
+
+
+ /**
+ * 公钥
+ */
+ private String publicKey;
+
+ /**
+ * 私钥
+ */
+ private String privateKey;
+}
diff --git a/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index fe37589da..b73a23480 100644
--- a/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/ruoyi-common/ruoyi-common-encrypt/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1 +1,2 @@
org.dromara.common.encrypt.config.EncryptorAutoConfiguration
+org.dromara.common.encrypt.config.ApiDecryptAutoConfiguration
diff --git a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
index 37c78c487..f6b997e1d 100644
--- a/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
+++ b/ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
@@ -36,41 +36,22 @@ public class LoginHelper {
public static final String TENANT_KEY = "tenantId";
public static final String USER_KEY = "userId";
- /**
- * 登录系统
- *
- * @param loginUser 登录用户信息
- */
- public static void login(LoginUser loginUser) {
- loginByDevice(loginUser, null);
- }
-
/**
* 登录系统 基于 设备类型
* 针对相同用户体系不同设备
*
* @param loginUser 登录用户信息
+ * @param model 配置参数
*/
- public static void loginByDevice(LoginUser loginUser, DeviceType deviceType) {
+ public static void login(LoginUser loginUser, SaLoginModel model) {
SaStorage storage = SaHolder.getStorage();
storage.set(LOGIN_USER_KEY, loginUser);
storage.set(TENANT_KEY, loginUser.getTenantId());
storage.set(USER_KEY, loginUser.getUserId());
- SaLoginModel model = new SaLoginModel();
- if (ObjectUtil.isNotNull(deviceType)) {
- model.setDevice(deviceType.getDevice());
- }
- // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
- // 例如: 后台用户30分钟过期 app用户1天过期
-// UserType userType = UserType.getUserType(loginUser.getUserType());
-// if (userType == UserType.SYS_USER) {
-// model.setTimeout(86400).setActiveTimeout(1800);
-// } else if (userType == UserType.APP_USER) {
-// model.setTimeout(86400).setActiveTimeout(1800);
-// }
+ model = ObjectUtil.defaultIfNull(model, new SaLoginModel());
StpUtil.login(loginUser.getLoginId(),
- model.setExtra(TENANT_KEY, loginUser.getTenantId())
- .setExtra(USER_KEY, loginUser.getUserId()));
+ model.setExtra(TENANT_KEY, loginUser.getTenantId())
+ .setExtra(USER_KEY, loginUser.getUserId()));
StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
}
diff --git a/ruoyi-common/ruoyi-common-social/pom.xml b/ruoyi-common/ruoyi-common-social/pom.xml
new file mode 100644
index 000000000..887a8e06f
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/pom.xml
@@ -0,0 +1,35 @@
+
+
+
+ org.dromara
+ ruoyi-common
+ ${revision}
+
+ 4.0.0
+
+ ruoyi-common-social
+
+
+ ruoyi-common-social 授权认证
+
+
+
+
+ me.zhyd.oauth
+ JustAuth
+
+
+
+ org.dromara
+ ruoyi-common-json
+
+
+
+ org.dromara
+ ruoyi-common-redis
+
+
+
+
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java
new file mode 100644
index 000000000..19b39d8fb
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/SocialAutoConfiguration.java
@@ -0,0 +1,23 @@
+package org.dromara.common.social.config;
+
+import me.zhyd.oauth.cache.AuthStateCache;
+import org.dromara.common.social.config.properties.SocialProperties;
+import org.dromara.common.social.utils.AuthRedisStateCache;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+
+/**
+ * Social 配置属性
+ * @author thiszhc
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(SocialProperties.class)
+public class SocialAutoConfiguration {
+
+ @Bean
+ public AuthStateCache authStateCache() {
+ return new AuthRedisStateCache();
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java
new file mode 100644
index 000000000..76be234c6
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java
@@ -0,0 +1,68 @@
+package org.dromara.common.social.config.properties;
+
+import lombok.Data;
+
+/**
+ * 社交登录配置
+ *
+ * @author thiszhc
+ */
+@Data
+public class SocialLoginConfigProperties {
+
+ /**
+ * 应用 ID
+ */
+ private String clientId;
+
+ /**
+ * 应用密钥
+ */
+ private String clientSecret;
+
+ /**
+ * 回调地址
+ */
+ private String redirectUri;
+
+ /**
+ * 是否获取unionId
+ */
+ private boolean unionId;
+
+ /**
+ * Coding 企业名称
+ */
+ private String codingGroupName;
+
+ /**
+ * 支付宝公钥
+ */
+ private String alipayPublicKey;
+
+ /**
+ * 企业微信应用ID
+ */
+ private String agentId;
+
+ /**
+ * stackoverflow api key
+ */
+ private String stackOverflowKey;
+
+ /**
+ * 设备ID
+ */
+ private String deviceId;
+
+ /**
+ * 客户端系统类型
+ */
+ private String clientOsType;
+
+ /**
+ * maxkey 服务器地址
+ */
+ private String serverUrl;
+
+}
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java
new file mode 100644
index 000000000..1beb7d042
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialProperties.java
@@ -0,0 +1,29 @@
+package org.dromara.common.social.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * Social 配置属性
+ *
+ * @author thiszhc
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "justauth")
+public class SocialProperties {
+
+ /**
+ * 是否启用
+ */
+ private Boolean enabled;
+
+ /**
+ * 授权类型
+ */
+ private Map type;
+
+}
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java
new file mode 100644
index 000000000..e8df79e5b
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeyRequest.java
@@ -0,0 +1,80 @@
+package org.dromara.common.social.maxkey;
+
+import cn.hutool.core.lang.Dict;
+import me.zhyd.oauth.cache.AuthStateCache;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+
+/**
+ * @author 长春叭哥 2023年03月26日
+ */
+public class AuthMaxKeyRequest extends AuthDefaultRequest {
+
+ public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.maxkey.server-url");
+
+ /**
+ * 设定归属域
+ */
+ public AuthMaxKeyRequest(AuthConfig config) {
+ super(config, AuthMaxKeySource.MAXKEY);
+ }
+
+ public AuthMaxKeyRequest(AuthConfig config, AuthStateCache authStateCache) {
+ super(config, AuthMaxKeySource.MAXKEY, authStateCache);
+ }
+
+ @Override
+ protected AuthToken getAccessToken(AuthCallback authCallback) {
+ String body = doPostAuthorizationCode(authCallback.getCode());
+ Dict object = JsonUtils.parseMap(body);
+ // oauth/token 验证异常
+ if (object.containsKey("error")) {
+ throw new AuthException(object.getStr("error_description"));
+ }
+ // user 验证异常
+ if (object.containsKey("message")) {
+ throw new AuthException(object.getStr("message"));
+ }
+ return AuthToken.builder()
+ .accessToken(object.getStr("access_token"))
+ .refreshToken(object.getStr("refresh_token"))
+ .idToken(object.getStr("id_token"))
+ .tokenType(object.getStr("token_type"))
+ .scope(object.getStr("scope"))
+ .build();
+ }
+
+ @Override
+ protected AuthUser getUserInfo(AuthToken authToken) {
+ String body = doGetUserInfo(authToken);
+ Dict object = JsonUtils.parseMap(body);
+ // oauth/token 验证异常
+ if (object.containsKey("error")) {
+ throw new AuthException(object.getStr("error_description"));
+ }
+ // user 验证异常
+ if (object.containsKey("message")) {
+ throw new AuthException(object.getStr("message"));
+ }
+ return AuthUser.builder()
+ .uuid(object.getStr("id"))
+ .username(object.getStr("username"))
+ .nickname(object.getStr("name"))
+ .avatar(object.getStr("avatar_url"))
+ .blog(object.getStr("web_url"))
+ .company(object.getStr("organization"))
+ .location(object.getStr("location"))
+ .email(object.getStr("email"))
+ .remark(object.getStr("bio"))
+ .token(authToken)
+ .source(source.toString())
+ .build();
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java
new file mode 100644
index 000000000..1ff57f7c5
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/maxkey/AuthMaxKeySource.java
@@ -0,0 +1,52 @@
+package org.dromara.common.social.maxkey;
+
+import me.zhyd.oauth.config.AuthSource;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+
+/**
+ * Oauth2 默认接口说明
+ *
+ * @author 长春叭哥 2023年03月26日
+ *
+ */
+public enum AuthMaxKeySource implements AuthSource {
+
+ /**
+ * 自己搭建的 maxkey 私服
+ */
+ MAXKEY {
+
+ /**
+ * 授权的api
+ */
+ @Override
+ public String authorize() {
+ return AuthMaxKeyRequest.SERVER_URL + "/sign/authz/oauth/v20/authorize";
+ }
+
+ /**
+ * 获取accessToken的api
+ */
+ @Override
+ public String accessToken() {
+ return AuthMaxKeyRequest.SERVER_URL + "/sign/authz/oauth/v20/token";
+ }
+
+ /**
+ * 获取用户信息的api
+ */
+ @Override
+ public String userInfo() {
+ return AuthMaxKeyRequest.SERVER_URL + "/sign/api/oauth/v20/me";
+ }
+
+ /**
+ * 平台对应的 AuthRequest 实现类,必须继承自 {@link AuthDefaultRequest}
+ */
+ @Override
+ public Class extends AuthDefaultRequest> getTargetClass() {
+ return AuthMaxKeyRequest.class;
+ }
+
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java
new file mode 100644
index 000000000..0b6ec2023
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/AuthRedisStateCache.java
@@ -0,0 +1,61 @@
+package org.dromara.common.social.utils;
+
+import lombok.AllArgsConstructor;
+import me.zhyd.oauth.cache.AuthStateCache;
+import org.dromara.common.core.constant.GlobalConstants;
+import org.dromara.common.redis.utils.RedisUtils;
+
+import java.time.Duration;
+
+/**
+ * 授权状态缓存
+ */
+@AllArgsConstructor
+public class AuthRedisStateCache implements AuthStateCache {
+
+ /**
+ * 存入缓存
+ *
+ * @param key 缓存key
+ * @param value 缓存内容
+ */
+ @Override
+ public void cache(String key, String value) {
+ // 授权超时时间 默认三分钟
+ RedisUtils.setCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key, value, Duration.ofMinutes(3));
+ }
+
+ /**
+ * 存入缓存
+ *
+ * @param key 缓存key
+ * @param value 缓存内容
+ * @param timeout 指定缓存过期时间(毫秒)
+ */
+ @Override
+ public void cache(String key, String value, long timeout) {
+ RedisUtils.setCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key, value, Duration.ofMillis(timeout));
+ }
+
+ /**
+ * 获取缓存内容
+ *
+ * @param key 缓存key
+ * @return 缓存内容
+ */
+ @Override
+ public String get(String key) {
+ return RedisUtils.getCacheObject(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key);
+ }
+
+ /**
+ * 是否存在key,如果对应key的value值已过期,也返回false
+ *
+ * @param key 缓存key
+ * @return true:存在key,并且value没过期;false:key不存在或者已过期
+ */
+ @Override
+ public boolean containsKey(String key) {
+ return RedisUtils.hasKey(GlobalConstants.SOCIAL_AUTH_CODE_KEY + key);
+ }
+}
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java
new file mode 100644
index 000000000..b3e801c1e
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java
@@ -0,0 +1,70 @@
+package org.dromara.common.social.utils;
+
+import cn.hutool.core.util.ObjectUtil;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.exception.AuthException;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.*;
+import org.dromara.common.core.domain.model.LoginBody;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
+import org.dromara.common.social.config.properties.SocialProperties;
+import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
+
+/**
+ * 认证授权工具类
+ *
+ * @author thiszhc
+ */
+public class SocialUtils {
+
+ private static final AuthRedisStateCache STATE_CACHE = SpringUtils.getBean(AuthRedisStateCache.class);
+
+ @SuppressWarnings("unchecked")
+ public static AuthResponse loginAuth(LoginBody loginBody, SocialProperties socialProperties) throws AuthException {
+ AuthRequest authRequest = getAuthRequest(loginBody.getSource(), socialProperties);
+ AuthCallback callback = new AuthCallback();
+ callback.setCode(loginBody.getSocialCode());
+ callback.setState(loginBody.getSocialState());
+ return authRequest.login(callback);
+ }
+
+ public static AuthRequest getAuthRequest(String source, SocialProperties socialProperties) throws AuthException {
+ SocialLoginConfigProperties obj = socialProperties.getType().get(source);
+ if (ObjectUtil.isNull(obj)) {
+ throw new AuthException("不支持的第三方登录类型");
+ }
+ String clientId = obj.getClientId();
+ String clientSecret = obj.getClientSecret();
+ String redirectUri = obj.getRedirectUri();
+ return switch (source.toLowerCase()) {
+ case "dingtalk" -> new AuthDingTalkRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "baidu" -> new AuthBaiduRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "github" -> new AuthGithubRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "gitee" -> new AuthGiteeRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "weibo" -> new AuthWeiboRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "coding" -> new AuthCodingRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "oschina" -> new AuthOschinaRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ // 支付宝在创建回调地址时,不允许使用localhost或者127.0.0.1,所以这儿的回调地址使用的局域网内的ip
+ case "alipay" -> new AuthAlipayRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), socialProperties.getType().get("alipay").getAlipayPublicKey(), STATE_CACHE);
+ case "qq" -> new AuthQqRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "wechat_open" -> new AuthWeChatOpenRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "taobao" -> new AuthTaobaoRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "douyin" -> new AuthDouyinRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "linkedin" -> new AuthLinkedinRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "microsoft" -> new AuthMicrosoftRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "renren" -> new AuthRenrenRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "stack_overflow" -> new AuthStackOverflowRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).stackOverflowKey("").build(), STATE_CACHE);
+ case "huawei" -> new AuthHuaweiRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "wechat_enterprise" -> new AuthWeChatEnterpriseQrcodeRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).agentId("").build(), STATE_CACHE);
+ case "gitlab" -> new AuthGitlabRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "wechat_mp" -> new AuthWeChatMpRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "aliyun" -> new AuthAliyunRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ case "maxkey" -> new AuthMaxKeyRequest(AuthConfig.builder().clientId(clientId).clientSecret(clientSecret).redirectUri(redirectUri).build(), STATE_CACHE);
+ default -> throw new AuthException("未获取到有效的Auth配置");
+ };
+ }
+}
+
diff --git a/ruoyi-common/ruoyi-common-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..fc544a0fd
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.social.config.SocialAutoConfiguration
diff --git a/ruoyi-gateway/pom.xml b/ruoyi-gateway/pom.xml
index 6ebad7df8..71e544c0f 100644
--- a/ruoyi-gateway/pom.xml
+++ b/ruoyi-gateway/pom.xml
@@ -62,11 +62,6 @@
spring-cloud-loadbalancer
-
- cn.hutool
- hutool-captcha
-
-
cn.hutool
hutool-http
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/RouterFunctionConfiguration.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/config/RouterFunctionConfiguration.java
deleted file mode 100644
index 00778f2eb..000000000
--- a/ruoyi-gateway/src/main/java/org/dromara/gateway/config/RouterFunctionConfiguration.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.dromara.gateway.config;
-
-import org.dromara.gateway.handler.ValidateCodeHandler;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.http.MediaType;
-import org.springframework.web.reactive.function.server.RequestPredicates;
-import org.springframework.web.reactive.function.server.RouterFunction;
-import org.springframework.web.reactive.function.server.RouterFunctions;
-
-/**
- * 路由配置信息
- *
- * @author ruoyi
- */
-@Configuration
-public class RouterFunctionConfiguration {
- @Autowired
- private ValidateCodeHandler validateCodeHandler;
-
- @SuppressWarnings("rawtypes")
- @Bean
- public RouterFunction routerFunction() {
- return RouterFunctions.route(
- RequestPredicates.GET("/code").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
- validateCodeHandler);
- }
-}
diff --git a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/ValidateCodeFilter.java b/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/ValidateCodeFilter.java
deleted file mode 100644
index 514eca901..000000000
--- a/ruoyi-gateway/src/main/java/org/dromara/gateway/filter/ValidateCodeFilter.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package org.dromara.gateway.filter;
-
-import cn.hutool.core.lang.Dict;
-import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.json.utils.JsonUtils;
-import org.dromara.gateway.config.properties.CaptchaProperties;
-import org.dromara.gateway.service.ValidateCodeService;
-import org.dromara.gateway.utils.WebFluxUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cloud.gateway.filter.GatewayFilter;
-import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
-import org.springframework.http.server.reactive.ServerHttpRequest;
-import org.springframework.stereotype.Component;
-
-/**
- * 验证码过滤器
- *
- * @author ruoyi
- */
-@Component
-public class ValidateCodeFilter extends AbstractGatewayFilterFactory