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 { 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åæ®µï¼åå°body转为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("对ä¸èµ·ï¼ä½ 没ææéç»å½å½åç§æ·ï¼"); } 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 å·²ç»å ç½®å¥½äºæ°æ® 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 å·²ç»å ç½®å¥½äºæ°æ® 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 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; } 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(); } } 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 { /** * ææçapi */ @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; } } } 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("æªè·åå°ææçAuthé ç½®"); }; } 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); } } 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); } 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; } } 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个keyè¢«æ´æ¹åè¿è¡å¿«ç § save 900 1