From 18e919bde3d925ee76fe29c7a6621c2716b1e4e4 Mon Sep 17 00:00:00 2001
From: baoshiwei <baoshiwei@shlanbao.cn>
Date: 星期五, 21 三月 2025 09:43:31 +0800
Subject: [PATCH] feat(social): 添加 Keycloak社交登录支持

---
 ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/keycloak/AuthKeycloakRequest.java                  |  144 ++++++++++++++++++++++++
 script/docker/redis/conf/redis.conf                                                                                         |    4 
 ruoyi-admin/src/main/resources/application-prod.yml                                                                         |   10 +
 ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java                                              |   82 ++++++++++++-
 ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java                                                      |   28 ++++
 ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/utils/SocialUtils.java                             |   16 ++
 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSocialServiceImpl.java                          |    7 +
 ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/keycloak/AuthKeycloakSource.java                   |   36 ++++++
 ruoyi-admin/src/main/resources/application.yml                                                                              |    4 
 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysSocialService.java                                  |    2 
 ruoyi-admin/src/main/resources/application-dev.yml                                                                          |   24 ++-
 ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/config/properties/SocialLoginConfigProperties.java |    2 
 12 files changed, 338 insertions(+), 21 deletions(-)

diff --git a/ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java b/ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java
index 41a802b..5966195 100644
--- a/ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java
+++ b/ruoyi-admin/src/main/java/org/dromara/web/service/SysLoginService.java
@@ -6,6 +6,8 @@
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.lang.Opt;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
 import com.baomidou.lock.annotation.Lock4j;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -49,6 +51,12 @@
 @Slf4j
 @Service
 public class SysLoginService {
+
+    @Value("${justauth.type.keycloak.server-url}")
+    private String keycloakServerUrl;
+
+    @Value("${justauth.type.keycloak.realm}")
+    private String keycloakRealm;
 
     @Value("${user.password.maxRetryCount}")
     private Integer maxRetryCount;
@@ -118,6 +126,26 @@
                 TenantHelper.clearDynamic();
             }
             recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
+            // 鏂板Keycloak鐧诲嚭閫昏緫
+
+                Long userId = loginUser.getUserId();
+
+                SysSocialVo social = sysSocialService.selectByUserId(userId);
+                if (social == null) {
+                    return;
+                }
+
+                String logoutUrl = keycloakServerUrl + "/realms/" + keycloakRealm + "/protocol/openid-connect/logout";
+            HttpRequest request = HttpRequest.get(logoutUrl)
+                .form("refresh_token", social.getRefreshToken())
+                .form("id_token_hint", social.getIdToken());
+
+
+                HttpResponse response = request.execute();
+                if (response.isOk()) {
+                    System.out.println("1234");
+                }
+
         } catch (NotLoginException ignored) {
         } finally {
             try {
diff --git a/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java b/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java
index 419dbd6..9d62dd8 100644
--- a/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java
+++ b/ruoyi-admin/src/main/java/org/dromara/web/service/impl/SocialAuthStrategy.java
@@ -10,9 +10,11 @@
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthToken;
 import me.zhyd.oauth.model.AuthUser;
 import org.dromara.common.core.constant.SystemConstants;
 import org.dromara.common.core.domain.model.LoginUser;
+import org.dromara.common.core.domain.model.PasswordLoginBody;
 import org.dromara.common.core.domain.model.SocialLoginBody;
 import org.dromara.common.core.exception.ServiceException;
 import org.dromara.common.core.exception.user.UserException;
@@ -21,8 +23,13 @@
 import org.dromara.common.json.utils.JsonUtils;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.common.social.config.properties.SocialProperties;
+import org.dromara.common.social.keycloak.AuthKeycloakRequest;
 import org.dromara.common.social.utils.SocialUtils;
 import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.domain.SysSocial;
+import org.dromara.system.domain.SysUser;
+import org.dromara.system.domain.bo.SysSocialBo;
+import org.dromara.system.domain.bo.SysUserBo;
 import org.dromara.system.domain.vo.SysClientVo;
 import org.dromara.system.domain.vo.SysSocialVo;
 import org.dromara.system.domain.vo.SysUserVo;
@@ -59,15 +66,34 @@
      */
     @Override
     public LoginVo login(String body, SysClientVo client) {
-        SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
-        ValidatorUtils.validate(loginBody);
-        AuthResponse<AuthUser> response = SocialUtils.loginAuth(
+        // 濡傛灉bodyp瀛楃涓蹭腑鍖呭惈login_type瀛楁锛屽垯灏哹ody杞负password鐧诲綍
+        AuthUser authUserData = null;
+        String tenantId = null;
+        if (body.contains("login_type")) {
+            PasswordLoginBody passwordLoginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
+            tenantId = passwordLoginBody.getTenantId();
+            ValidatorUtils.validate(passwordLoginBody);
+            AuthKeycloakRequest authRequest = SocialUtils.getAuthKeyloakRequest("keycloak", socialProperties);
+            AuthToken accessToken = authRequest.getAccessToken(passwordLoginBody);
+            authUserData = authRequest.getUserInfo(accessToken);
+
+
+        } else {
+            SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
+            tenantId =loginBody.getTenantId();
+                ValidatorUtils.validate(loginBody);
+            AuthResponse<AuthUser> response = SocialUtils.loginAuth(
                 loginBody.getSource(), loginBody.getSocialCode(),
                 loginBody.getSocialState(), socialProperties);
-        if (!response.ok()) {
-            throw new ServiceException(response.getMsg());
+            if (!response.ok()) {
+                throw new ServiceException(response.getMsg());
+            }
+            authUserData = response.getData();
         }
-        AuthUser authUserData = response.getData();
+
+
+
+
         if ("GITEE".equals(authUserData.getSource())) {
             // 濡傜敤鎴蜂娇鐢� gitee 鐧诲綍椤烘墜 star 缁欎綔鑰呬竴鐐规敮鎸� 鎷掔粷鐧藉珫
             HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Vue-Plus")
@@ -77,6 +103,47 @@
                     .formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
                     .executeAsync();
         }
+        if ("KEYCLOAK".equals(authUserData.getSource())) {
+            // 鏂板KEYCLOAK鐢ㄦ埛鑷姩鍒涘缓閫昏緫
+            String authId = authUserData.getSource() + authUserData.getUuid();
+            List<SysSocialVo> list = sysSocialService.selectByAuthId(authId);
+            if (CollUtil.isEmpty(list)) {
+                // 鑷姩鍒涘缓鏂扮敤鎴�
+                SysUser newUser = new SysUser();
+                newUser.setUserName(authUserData.getUsername());
+                newUser.setEmail(authUserData.getEmail());
+                newUser.setNickName(authUserData.getNickname());
+                newUser.setPassword("Initial123@"); // 鍒濆瀵嗙爜闇�绗﹀悎瀹夊叏绛栫暐
+                newUser.setStatus(SystemConstants.NORMAL);
+
+                userMapper.insert(newUser); // 鍋囪瀛樺湪鎻掑叆鏂规硶
+
+                // 鍒涘缓绀句氦缁戝畾璁板綍
+                SysSocialBo newSocial = new SysSocialBo();
+                newSocial.setUserId(newUser.getUserId());
+                newSocial.setUserName(newUser.getUserName());
+                newSocial.setAuthId(authId);
+                newSocial.setSource(authUserData.getSource());
+                newSocial.setTenantId(newUser.getTenantId());
+                newSocial.setOpenId(authUserData.getUuid());
+                newSocial.setAccessToken(authUserData.getToken().getAccessToken());
+                newSocial.setRefreshToken(authUserData.getToken().getRefreshToken());
+                newSocial.setIdToken(authUserData.getToken().getIdToken());
+                sysSocialService.insertByBo(newSocial); // 闇�纭繚鏈嶅姟鏈夋柊澧炴柟娉�
+
+                // 閲嶆柊鏌ヨ纭繚鏁版嵁鍙敤
+                list = sysSocialService.selectByAuthId(authId);
+            } else {
+                // 鏇存柊绀句氦缁戝畾璁板綍
+                SysSocialBo socialBo = new SysSocialBo();
+                socialBo.setId(list.get(0).getId());
+                socialBo.setAccessToken(authUserData.getToken().getAccessToken());
+                socialBo.setRefreshToken(authUserData.getToken().getRefreshToken());
+                socialBo.setIdToken(authUserData.getToken().getIdToken());
+                sysSocialService.updateByBo(socialBo);
+
+            }
+        }
 
         List<SysSocialVo> list = sysSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
         if (CollUtil.isEmpty(list)) {
@@ -84,7 +151,8 @@
         }
         SysSocialVo social;
         if (TenantHelper.isEnable()) {
-            Optional<SysSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId()));
+            String finalTenantId = tenantId;
+            Optional<SysSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(finalTenantId));
             if (opt.isEmpty()) {
                 throw new ServiceException("瀵逛笉璧凤紝浣犳病鏈夋潈闄愮櫥褰曞綋鍓嶇鎴凤紒");
             }
diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml
index 8bc5b94..fb3a3db 100644
--- a/ruoyi-admin/src/main/resources/application-dev.yml
+++ b/ruoyi-admin/src/main/resources/application-dev.yml
@@ -49,17 +49,17 @@
           driverClassName: com.mysql.cj.jdbc.Driver
           # jdbc 鎵�鏈夊弬鏁伴厤缃弬鑰� https://lionli.blog.csdn.net/article/details/122018562
           # rewriteBatchedStatements=true 鎵瑰鐞嗕紭鍖� 澶у箙鎻愬崌鎵归噺鎻掑叆鏇存柊鍒犻櫎鎬ц兘(瀵规暟鎹簱鏈夋�ц兘鎹熻�� 浣跨敤鎵归噺鎿嶄綔搴旇�冭檻鎬ц兘闂)
-          url: jdbc:mysql://192.168.12.240:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
           username: root
           password: 123456
         # 浠庡簱鏁版嵁婧�
-        slave:
-          lazy: true
-          type: ${spring.datasource.type}
-          driverClassName: com.mysql.cj.jdbc.Driver
-          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
-          username:
-          password:
+#        slave:
+#          lazy: true
+#          type: ${spring.datasource.type}
+#          driverClassName: com.mysql.cj.jdbc.Driver
+#          url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
+#          username:
+#          password:
 #        oracle:
 #          type: ${spring.datasource.type}
 #          driverClassName: oracle.jdbc.OracleDriver
@@ -193,6 +193,14 @@
   # 鍓嶇澶栫綉璁块棶鍦板潃
   address: http://localhost:80
   type:
+    keycloak:
+      # keycloak 鏈嶅姟鍣ㄥ湴鍧�
+      server-url: https://lanbaosystem.shlanbao.cn:8443
+      realm: lanbao
+      client-id: DataCapture
+      client-secret: kplisa4lJHEIM6knqefVbxln85QbA5NX
+      redirect-uri: ${justauth.address}/social-callback
+      scopes: [openid, email, phone, profile]
     maxkey:
       # maxkey 鏈嶅姟鍣ㄥ湴鍧�
       # 娉ㄦ剰 濡備笅鍧囬厤缃潎涓嶉渶瑕佷慨鏀� maxkey 宸茬粡鍐呯疆濂戒簡鏁版嵁
diff --git a/ruoyi-admin/src/main/resources/application-prod.yml b/ruoyi-admin/src/main/resources/application-prod.yml
index 80c1f96..c63e21a 100644
--- a/ruoyi-admin/src/main/resources/application-prod.yml
+++ b/ruoyi-admin/src/main/resources/application-prod.yml
@@ -193,8 +193,16 @@
 --- # 涓夋柟鎺堟潈
 justauth:
   # 鍓嶇澶栫綉璁块棶鍦板潃
-  address: http://localhost:80
+  address: http://192.168.0.23:80
   type:
+    keycloak:
+      # keycloak 鏈嶅姟鍣ㄥ湴鍧�
+      server-url: https://lanbaosystem.shlanbao.cn:8443
+      realm: lanbao
+      client-id: DataCapture
+      client-secret: kplisa4lJHEIM6knqefVbxln85QbA5NX
+      redirect-uri: ${justauth.address}/social-callback
+      scopes: [openid, email, phone, profile]
     maxkey:
       # maxkey 鏈嶅姟鍣ㄥ湴鍧�
       # 娉ㄦ剰 濡備笅鍧囬厤缃潎涓嶉渶瑕佷慨鏀� maxkey 宸茬粡鍐呯疆濂戒簡鏁版嵁
diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml
index f293140..5a74577 100644
--- a/ruoyi-admin/src/main/resources/application.yml
+++ b/ruoyi-admin/src/main/resources/application.yml
@@ -1,11 +1,11 @@
 # 椤圭洰鐩稿叧閰嶇疆
 ruoyi:
   # 鍚嶇О
-  name: RuoYi-Vue-Plus
+  name: Lanbao_QMS
   # 鐗堟湰
   version: ${revision}
   # 鐗堟潈骞翠唤
-  copyrightYear: 2024
+  copyrightYear: 2025
 
 captcha:
   enable: false
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
index 5f49d9c..ec02328 100644
--- 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
@@ -72,4 +72,6 @@
      */
     private List<String> scopes;
 
+    private String realm;
+
 }
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/keycloak/AuthKeycloakRequest.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/keycloak/AuthKeycloakRequest.java
new file mode 100644
index 0000000..f3da8a1
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/keycloak/AuthKeycloakRequest.java
@@ -0,0 +1,144 @@
+package org.dromara.common.social.keycloak;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.lang.Dict;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import com.xkcoding.http.support.HttpHeader;
+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 me.zhyd.oauth.utils.HttpUtils;
+import me.zhyd.oauth.utils.UrlBuilder;
+import org.dromara.common.core.domain.model.PasswordLoginBody;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+
+/**
+ * Keycloak OAuth2 璁よ瘉璇锋眰
+ */
+public class AuthKeycloakRequest extends AuthDefaultRequest {
+
+    public static final String SERVER_URL = SpringUtils.getProperty("justauth.type.keycloak.server-url");
+    public static final String REALM = SpringUtils.getProperty("justauth.type.keycloak.realm");
+
+    public AuthKeycloakRequest(AuthConfig config) {
+        super(config, AuthKeycloakSource.KEYCLOAK);
+    }
+
+    public AuthKeycloakRequest(AuthConfig config, AuthStateCache authStateCache) {
+        super(config, AuthKeycloakSource.KEYCLOAK, authStateCache);
+    }
+
+
+    public  AuthToken getAccessToken(PasswordLoginBody loginBody) {
+
+        HttpRequest request = HttpRequest.post(SERVER_URL + "/realms/" + REALM + "/protocol/openid-connect/token")
+            .form("grant_type", "password")
+            .form("client_id", config.getClientId())
+            .form("client_secret", config.getClientSecret())
+            .form("username", loginBody.getUsername())
+            .form("password", loginBody.getPassword())
+            .form("scope", "openid");
+
+        HttpResponse response = request.execute();
+
+        Dict object = JsonUtils.parseMap(response.body());
+
+        if (object.containsKey("error")) {
+            throw new AuthException(object.getStr("error_description"));
+        }
+        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"))
+            .expireIn(object.getInt("expires_in"))
+            .build();
+    }
+
+    @Override
+    public AuthToken getAccessToken(AuthCallback authCallback) {
+        String body = doPostAuthorizationCode(authCallback.getCode());
+        Dict object = JsonUtils.parseMap(body);
+
+        if (object.containsKey("error")) {
+            throw new AuthException(object.getStr("error_description"));
+        }
+        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"))
+            .expireIn(object.getInt("expires_in"))
+            .build();
+    }
+
+    @Override
+    public AuthUser getUserInfo(AuthToken authToken) {
+        String body = doGetUserInfo(authToken);
+        Dict object = JsonUtils.parseMap(body);
+
+        if (object.containsKey("error")) {
+            throw new AuthException(object.getStr("error_description"));
+        }
+        if (object.containsKey("message")) {
+            throw new AuthException(object.getStr("message"));
+        }
+
+        return AuthUser.builder()
+            .uuid(object.getStr("sub"))
+            .username(object.getStr("preferred_username"))
+            .nickname(object.getStr("name"))
+            .email(object.getStr("email"))
+            .token(authToken)
+            .source(this.source.toString())
+            .build();
+    }
+
+    @Override
+    protected String doPostAuthorizationCode(String code) {
+        HttpRequest request = HttpRequest.post(source.accessToken())
+            .header("Authorization", "Basic " + Base64.encode("%s:%s".formatted(config.getClientId(), config.getClientSecret())))
+            .form("grant_type", "authorization_code")
+            .form("code", code)
+            .form("redirect_uri", config.getRedirectUri());
+        HttpResponse response = request.execute();
+        return response.body();
+    }
+
+    @Override
+    protected String doGetUserInfo(AuthToken authToken) {
+        try {
+            return new HttpUtils(config.getHttpConfig()).get(source.userInfo(), null, new HttpHeader()
+                .add("Content-Type", "application/json")
+                .add("Authorization", "Bearer " + authToken.getAccessToken()), false).getBody();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    @Override
+    public String authorize(String state) {
+        return UrlBuilder.fromBaseUrl(super.authorize(state))
+            .queryParam("scope", StrUtil.join("%20", config.getScopes()))
+            .build();
+    }
+
+
+
+
+}
diff --git a/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/keycloak/AuthKeycloakSource.java b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/keycloak/AuthKeycloakSource.java
new file mode 100644
index 0000000..e232c34
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-social/src/main/java/org/dromara/common/social/keycloak/AuthKeycloakSource.java
@@ -0,0 +1,36 @@
+package org.dromara.common.social.keycloak;
+
+import me.zhyd.oauth.config.AuthSource;
+import me.zhyd.oauth.request.AuthDefaultRequest;
+
+public enum AuthKeycloakSource implements AuthSource {
+    KEYCLOAK {
+        /**
+         * 鎺堟潈鐨刟pi
+         */
+        @Override
+        public String authorize() {
+            return String.format("%s/realms/%s/protocol/openid-connect/auth", AuthKeycloakRequest.SERVER_URL, AuthKeycloakRequest.REALM);
+        }
+
+        @Override
+        public String accessToken() {
+            return String.format("%s/realms/%s/protocol/openid-connect/token", AuthKeycloakRequest.SERVER_URL, AuthKeycloakRequest.REALM);
+        }
+
+        @Override
+        public String userInfo() {
+            return String.format("%s/realms/%s/protocol/openid-connect/userinfo", AuthKeycloakRequest.SERVER_URL, AuthKeycloakRequest.REALM);
+        }
+
+
+        public String logout() {
+            return String.format("%s/realms/%s/protocol/openid-connect/logout", AuthKeycloakRequest.SERVER_URL, AuthKeycloakRequest.REALM);
+        }
+
+        @Override
+        public Class<? extends AuthDefaultRequest> getTargetClass() {
+            return AuthKeycloakRequest.class;
+        }
+    }
+}
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
index db696e5..5a6b370 100644
--- 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
@@ -10,6 +10,7 @@
 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.keycloak.AuthKeycloakRequest;
 import org.dromara.common.social.maxkey.AuthMaxKeyRequest;
 import org.dromara.common.social.topiam.AuthTopIamRequest;
 
@@ -66,8 +67,23 @@
             case "aliyun" -> new AuthAliyunRequest(builder.build(), STATE_CACHE);
             case "maxkey" -> new AuthMaxKeyRequest(builder.build(), STATE_CACHE);
             case "topiam" -> new AuthTopIamRequest(builder.build(), STATE_CACHE);
+            case "keycloak" -> new AuthKeycloakRequest(builder.build(), STATE_CACHE);
             default -> throw new AuthException("鏈幏鍙栧埌鏈夋晥鐨凙uth閰嶇疆");
         };
     }
+
+
+    public static AuthKeycloakRequest getAuthKeyloakRequest(String source, SocialProperties socialProperties) {
+        SocialLoginConfigProperties obj = socialProperties.getType().get(source);
+        if (ObjectUtil.isNull(obj)) {
+            throw new AuthException("涓嶆敮鎸佺殑绗笁鏂圭櫥褰曠被鍨�");
+        }
+        AuthConfig.AuthConfigBuilder builder = AuthConfig.builder()
+            .clientId(obj.getClientId())
+            .clientSecret(obj.getClientSecret())
+            .redirectUri(obj.getRedirectUri())
+            .scopes(obj.getScopes());
+        return new AuthKeycloakRequest(builder.build(), STATE_CACHE);
+    }
 }
 
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysSocialService.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysSocialService.java
index cc7016e..4ec49aa 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysSocialService.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ISysSocialService.java
@@ -49,5 +49,5 @@
      */
     List<SysSocialVo> selectByAuthId(String authId);
 
-
+    SysSocialVo selectByUserId(Long userId);
 }
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSocialServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSocialServiceImpl.java
index 9c54cbc..7fe35b9 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSocialServiceImpl.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysSocialServiceImpl.java
@@ -109,4 +109,11 @@
         return baseMapper.selectVoList(new LambdaQueryWrapper<SysSocial>().eq(SysSocial::getAuthId, authId));
     }
 
+
+    @Override
+    public SysSocialVo selectByUserId(Long userId) {
+        SysSocialVo socialVo = baseMapper.selectVoOne(new LambdaQueryWrapper<SysSocial>().eq(SysSocial::getUserId, userId));
+        return socialVo;
+    }
+
 }
diff --git a/script/docker/redis/conf/redis.conf b/script/docker/redis/conf/redis.conf
index 72255c6..4af3b79 100644
--- a/script/docker/redis/conf/redis.conf
+++ b/script/docker/redis/conf/redis.conf
@@ -1,11 +1,11 @@
 # redis 瀵嗙爜
-requirepass ruoyi123
+#requirepass ruoyi123
 
 # key 鐩戝惉鍣ㄩ厤缃�
 # notify-keyspace-events Ex
 
 # 閰嶇疆鎸佷箙鍖栨枃浠跺瓨鍌ㄨ矾寰�
-dir /redis/data
+dir /data/redis/data
 # 閰嶇疆rdb
 # 15鍒嗛挓鍐呮湁鑷冲皯1涓猭ey琚洿鏀瑰垯杩涜蹇収
 save 900 1

--
Gitblit v1.9.3