Commit 0d6df43c9c948aafc77258c508f6ce6ea668364c

Authored by YunaiV
1 parent a0a5d3a3

1. 优化三方登陆的代码

2. 使用 redis 存储 state
Showing 18 changed files with 280 additions and 175 deletions
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/auth/SysAuthController.java
... ... @@ -11,6 +11,7 @@ import cn.iocoder.yudao.adminserver.modules.system.enums.user.SysSocialTypeEnum;
11 11 import cn.iocoder.yudao.adminserver.modules.system.service.auth.SysAuthService;
12 12 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
13 13 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysRoleService;
  14 +import cn.iocoder.yudao.adminserver.modules.system.service.social.SysSocialService;
14 15 import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
15 16 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
16 17 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
... ... @@ -56,9 +57,8 @@ public class SysAuthController {
56 57 private SysRoleService roleService;
57 58 @Resource
58 59 private SysPermissionService permissionService;
59   -
60 60 @Resource
61   - private AuthRequestFactory authRequestFactory;
  61 + private SysSocialService socialService;
62 62  
63 63 @PostMapping("/login")
64 64 @ApiOperation("使用账号密码登录")
... ... @@ -110,12 +110,7 @@ public class SysAuthController {
110 110 })
111 111 public CommonResult<String> socialLoginRedirect(@RequestParam("type") Integer type,
112 112 @RequestParam("redirectUri") String redirectUri) {
113   - // 获得对应的 AuthRequest 实现
114   - AuthRequest authRequest = authRequestFactory.get(SysSocialTypeEnum.valueOfType(type).getSource());
115   - // 生成跳转地址
116   - String authorizeUri = authRequest.authorize(AuthStateUtils.createState());
117   - authorizeUri = HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri);
118   - return CommonResult.success(authorizeUri);
  113 + return CommonResult.success(socialService.getAuthorizeUrl(type, redirectUri));
119 114 }
120 115  
121 116 @PostMapping("/social-login")
... ... @@ -136,12 +131,4 @@ public class SysAuthController {
136 131 return success(SysAuthLoginRespVO.builder().token(token).build());
137 132 }
138 133  
139   - @RequestMapping("/{type}/callback")
140   - public AuthResponse login(@PathVariable String type, AuthCallback callback) {
141   - AuthRequest authRequest = authRequestFactory.get(type);
142   - AuthResponse<AuthUser> response = authRequest.login(callback);
143   - log.info("【response】= {}", JSONUtil.toJsonStr(response));
144   - return response;
145   - }
146   -
147 134 }
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/user/vo/user/AuthUser.java deleted
1   -package cn.iocoder.yudao.adminserver.modules.system.controller.user.vo.user;
2   -
3   -/**
4   - * @author weir
5   - */
6   -public class AuthUser {
7   -}
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/convert/auth/SysAuthConvert.java
... ... @@ -43,10 +43,6 @@ public interface SysAuthConvert {
43 43  
44 44 LoginUser convert(SysUserProfileUpdatePasswordReqVO reqVO);
45 45  
46   - AuthCallback convert(SysAuthSocialLoginReqVO bean);
47   -
48   - AuthCallback convert(SysAuthSocialLogin2ReqVO bean);
49   -
50 46 /**
51 47 * 将菜单列表,构建成菜单树
52 48 *
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/dal/dataobject/user/SysSocialUserDO.java renamed to yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/dal/dataobject/social/SysSocialUserDO.java
1   -package cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user;
  1 +package cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social;
2 2  
  3 +import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysUserDO;
3 4 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
4 5 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
5 6 import com.baomidou.mybatisplus.annotation.TableId;
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/dal/mysql/user/SysSocialUserMapper.java renamed to yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/dal/mysql/social/SysSocialUserMapper.java
1   -package cn.iocoder.yudao.adminserver.modules.system.dal.mysql.user;
  1 +package cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social;
2 2  
3   -import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysSocialUserDO;
  3 +import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social.SysSocialUserDO;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
6 6 import org.apache.ibatis.annotations.Mapper;
7 7  
  8 +import java.util.Collection;
8 9 import java.util.List;
9 10  
10 11 @Mapper
11 12 public interface SysSocialUserMapper extends BaseMapperX<SysSocialUserDO> {
12 13  
13   - default List<SysSocialUserDO> selectListByTypeAndUnionId(Integer userType, Integer type, String unionId) {
  14 + default List<SysSocialUserDO> selectListByTypeAndUnionId(Integer userType, Collection<Integer> types, String unionId) {
14 15 return selectList(new QueryWrapper<SysSocialUserDO>().eq("user_type", userType)
15   - .eq("type", type).eq("union_id", unionId));
  16 + .in("type", types).eq("union_id", unionId));
  17 + }
  18 +
  19 + default List<SysSocialUserDO> selectListByTypeAndUserId(Integer userType, Collection<Integer> types, Long userId) {
  20 + return selectList(new QueryWrapper<SysSocialUserDO>().eq("user_type", userType)
  21 + .in("type", types).eq("user_id", userId));
16 22 }
17 23  
18 24 }
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/dal/mysql/social/SysSysUserSocialMapper.java deleted
1   -package cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social;
2   -
3   -import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysSocialUserDO;
4   -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5   -import org.apache.ibatis.annotations.Mapper;
6   -
7   -@Mapper
8   -public interface SysSysUserSocialMapper extends BaseMapperX<SysSocialUserDO> {
9   -
10   -
11   -}
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/dal/mysql/social/SysUserSocialMapper.java deleted
1   -package cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social;
2   -
3   -import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysSocialUserDO;
4   -import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5   -import org.apache.ibatis.annotations.Mapper;
6   -
7   -@Mapper
8   -public interface SysUserSocialMapper extends BaseMapperX<SysSocialUserDO> {
9   -
10   -
11   -}
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/dal/redis/SysRedisKeyConstants.java
... ... @@ -23,8 +23,12 @@ public interface SysRedisKeyConstants {
23 23 "captcha_code:%s", // 参数为 uuid
24 24 STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
25 25  
26   - RedisKeyDefine AUTH_SOCIAL_USER = new RedisKeyDefine("认证的社交用户",
27   - "auth_social_user:%d:%s", // 参数为 type,code
  26 + RedisKeyDefine SOCIAL_AUTH_USER = new RedisKeyDefine("社交的授权用户",
  27 + "social_auth_user:%d:%s", // 参数为 type,code
28 28 STRING, AuthUser.class, Duration.ofDays(1));
29 29  
  30 + RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交的 state",
  31 + "social_auth_state:%s", // 参数为 state
  32 + STRING, String.class, Duration.ofHours(24)); // 值为 state
  33 +
30 34 }
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/dal/redis/auth/SysAuthSocialUserRedisDAO.java renamed to yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/dal/redis/social/SysSocialAuthUserRedisDAO.java
1   -package cn.iocoder.yudao.adminserver.modules.system.dal.redis.auth;
  1 +package cn.iocoder.yudao.adminserver.modules.system.dal.redis.social;
2 2  
3 3 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
4 4 import me.zhyd.oauth.model.AuthCallback;
... ... @@ -8,7 +8,7 @@ import org.springframework.stereotype.Repository;
8 8  
9 9 import javax.annotation.Resource;
10 10  
11   -import static cn.iocoder.yudao.adminserver.modules.system.dal.redis.SysRedisKeyConstants.AUTH_SOCIAL_USER;
  11 +import static cn.iocoder.yudao.adminserver.modules.system.dal.redis.SysRedisKeyConstants.SOCIAL_AUTH_USER;
12 12  
13 13 /**
14 14 * 社交 {@link me.zhyd.oauth.model.AuthUser} 的 RedisDAO
... ... @@ -16,7 +16,7 @@ import static cn.iocoder.yudao.adminserver.modules.system.dal.redis.SysRedisKeyC
16 16 * @author 芋道源码
17 17 */
18 18 @Repository
19   -public class SysAuthSocialUserRedisDAO {
  19 +public class SysSocialAuthUserRedisDAO {
20 20  
21 21 @Resource
22 22 private StringRedisTemplate stringRedisTemplate;
... ... @@ -28,11 +28,11 @@ public class SysAuthSocialUserRedisDAO {
28 28  
29 29 public void set(Integer type, AuthCallback authCallback, AuthUser authUser) {
30 30 String redisKey = formatKey(type, authCallback.getCode());
31   - stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(authUser), AUTH_SOCIAL_USER.getTimeout());
  31 + stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(authUser), SOCIAL_AUTH_USER.getTimeout());
32 32 }
33 33  
34 34 private static String formatKey(Integer type, String code) {
35   - return String.format(AUTH_SOCIAL_USER.getKeyTemplate(), type, code);
  35 + return String.format(SOCIAL_AUTH_USER.getKeyTemplate(), type, code);
36 36 }
37 37  
38 38 }
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/SysErrorCodeConstants.java
... ... @@ -17,7 +17,6 @@ public interface SysErrorCodeConstants {
17 17 ErrorCode AUTH_LOGIN_CAPTCHA_NOT_FOUND = new ErrorCode(1002000003, "验证码不存在");
18 18 ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1002000004, "验证码不正确");
19 19 ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1002000005, "未绑定账号,需要进行绑定");
20   - ErrorCode AUTH_THIRD_OAUTH_FAILURE = new ErrorCode(1002000006, "社交授权失败,原因是:{}");
21 20  
22 21 // ========== TOKEN 模块 1002001000 ==========
23 22 ErrorCode TOKEN_EXPIRED = new ErrorCode(1002001000, "Token 已经过期");
... ... @@ -98,4 +97,7 @@ public interface SysErrorCodeConstants {
98 97 ErrorCode ERROR_CODE_NOT_EXISTS = new ErrorCode(1002013000, "错误码不存在");
99 98 ErrorCode ERROR_CODE_DUPLICATE = new ErrorCode(1002013001, "已经存在编码为【{}】的错误码");
100 99  
  100 + // ========== 社交模块 1002014000 ==========
  101 + ErrorCode SOCIAL_AUTH_FAILURE = new ErrorCode(1002014000, "社交授权失败,原因是:{}");
  102 +
101 103 }
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/enums/user/SysSocialTypeEnum.java
1 1 package cn.iocoder.yudao.adminserver.modules.system.enums.user;
2 2  
  3 +import cn.hutool.core.collection.ListUtil;
3 4 import cn.hutool.core.util.ArrayUtil;
4 5 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
5 6 import lombok.AllArgsConstructor;
6 7 import lombok.Getter;
7 8  
8 9 import java.util.Arrays;
  10 +import java.util.List;
9 11  
10 12 /**
11   - * 社交用户的类型枚举
  13 + * 社交平台的类型枚举
12 14 *
13 15 * @author 芋道源码
14 16 */
... ... @@ -23,6 +25,8 @@ public enum SysSocialTypeEnum implements IntArrayValuable {
23 25  
24 26 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SysSocialTypeEnum::getType).toArray();
25 27  
  28 + public static final List<Integer> WECHAT_ALL = ListUtil.toList(WECHAT_ENTERPRISE.type);
  29 +
26 30 /**
27 31 * 类型
28 32 */
... ... @@ -41,4 +45,11 @@ public enum SysSocialTypeEnum implements IntArrayValuable {
41 45 return ArrayUtil.firstMatch(o -> o.getType().equals(type), values());
42 46 }
43 47  
  48 + public static List<Integer> getRelationTypes(Integer type) {
  49 + if (WECHAT_ALL.contains(type)) {
  50 + return WECHAT_ALL;
  51 + }
  52 + return ListUtil.toList(type);
  53 + }
  54 +
44 55 }
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/impl/SysAuthServiceImpl.java
1 1 package cn.iocoder.yudao.adminserver.modules.system.service.auth.impl;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4   -import cn.hutool.core.util.StrUtil;
5 4 import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthSocialLogin2ReqVO;
6 5 import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthSocialLoginReqVO;
7   -import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysSocialUserDO;
8   -import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.user.SysSocialUserMapper;
9   -import cn.iocoder.yudao.adminserver.modules.system.dal.redis.auth.SysAuthSocialUserRedisDAO;
10   -import cn.iocoder.yudao.adminserver.modules.system.enums.user.SysSocialTypeEnum;
  6 +import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social.SysSocialUserDO;
  7 +import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social.SysSocialUserMapper;
  8 +import cn.iocoder.yudao.adminserver.modules.system.service.social.SysSocialService;
11 9 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
12   -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
13 10 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
14   -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
15 11 import cn.iocoder.yudao.framework.security.core.LoginUser;
16 12 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
17 13 import cn.iocoder.yudao.adminserver.modules.system.controller.auth.vo.auth.SysAuthLoginReqVO;
... ... @@ -27,12 +23,8 @@ import cn.iocoder.yudao.adminserver.modules.system.service.logger.SysLoginLogSer
27 23 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
28 24 import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
29 25 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
30   -import com.xkcoding.justauth.AuthRequestFactory;
31 26 import lombok.extern.slf4j.Slf4j;
32   -import me.zhyd.oauth.model.AuthCallback;
33   -import me.zhyd.oauth.model.AuthResponse;
34 27 import me.zhyd.oauth.model.AuthUser;
35   -import me.zhyd.oauth.request.AuthRequest;
36 28 import org.springframework.context.annotation.Lazy;
37 29 import org.springframework.security.authentication.AuthenticationManager;
38 30 import org.springframework.security.authentication.BadCredentialsException;
... ... @@ -47,7 +39,6 @@ import org.springframework.util.Assert;
47 39  
48 40 import javax.annotation.Resource;
49 41 import java.util.List;
50   -import java.util.Objects;
51 42 import java.util.Set;
52 43  
53 44 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -77,14 +68,11 @@ public class SysAuthServiceImpl implements SysAuthService {
77 68 private SysLoginLogService loginLogService;
78 69 @Resource
79 70 private SysUserSessionService userSessionService;
80   -
81   - @Resource
82   - private SysAuthSocialUserRedisDAO authSocialUserRedisDAO;
83 71 @Resource
84   - private SysSocialUserMapper socialUserMapper;
  72 + private SysSocialService socialService;
85 73  
86 74 @Resource
87   - private AuthRequestFactory authRequestFactory;
  75 + private SysSocialUserMapper socialUserMapper;
88 76  
89 77 @Override
90 78 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
... ... @@ -189,13 +177,12 @@ public class SysAuthServiceImpl implements SysAuthService {
189 177 @Override
190 178 public String socialLogin(SysAuthSocialLoginReqVO reqVO, String userIp, String userAgent) {
191 179 // 使用 code 授权码,进行登陆
192   - AuthCallback authCallback = SysAuthConvert.INSTANCE.convert(reqVO);
193   - AuthUser authUser = this.obtainAuthUserFromCache(reqVO.getType(), authCallback);
  180 + AuthUser authUser = socialService.getAuthUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
  181 + Assert.notNull(authUser, "授权用户不为空");
194 182  
195 183 // 如果未绑定 SysSocialUserDO 用户,则无法自动登陆,进行报错
196   - String unionId = getAuthUserUnionId(authUser);
197   - List<SysSocialUserDO> socialUsers = socialUserMapper.selectListByTypeAndUnionId(UserTypeEnum.ADMIN.getValue(),
198   - reqVO.getType(), unionId);
  184 + String unionId = socialService.getAuthUserUnionId(authUser);
  185 + List<SysSocialUserDO> socialUsers = socialService.getAllSocialUserList(reqVO.getType(), unionId);
199 186 if (CollUtil.isEmpty(socialUsers)) {
200 187 throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
201 188 }
... ... @@ -209,8 +196,8 @@ public class SysAuthServiceImpl implements SysAuthService {
209 196 // TODO 芋艿:需要改造下,增加各种登陆方式
210 197 loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
211 198  
212   - // 保存社交用户
213   - this.saveSocialUser(reqVO.getType(), socialUsers, loginUser.getId(), authUser);
  199 + // 绑定社交用户(更新)
  200 + socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser);
214 201  
215 202 // 缓存登陆用户到 Redis 中,返回 sessionId 编号
216 203 return userSessionService.createUserSession(loginUser, userIp, userAgent);
... ... @@ -219,91 +206,20 @@ public class SysAuthServiceImpl implements SysAuthService {
219 206 @Override
220 207 public String socialLogin2(SysAuthSocialLogin2ReqVO reqVO, String userIp, String userAgent) {
221 208 // 使用 code 授权码,进行登陆
222   - AuthCallback authCallback = SysAuthConvert.INSTANCE.convert(reqVO);
223   - AuthUser authUser = this.obtainAuthUserFromCache(reqVO.getType(), authCallback);
224   -
225   - // 查询社交对应的 SysSocialUserDO 用户
226   - String unionId = getAuthUserUnionId(authUser);
227   - List<SysSocialUserDO> socialUsers = socialUserMapper.selectListByTypeAndUnionId(UserTypeEnum.ADMIN.getValue(),
228   - reqVO.getType(), unionId);
  209 + AuthUser authUser = socialService.getAuthUser(reqVO.getType(), reqVO.getCode(), reqVO.getState());
  210 + Assert.notNull(authUser, "授权用户不为空");
229 211  
230 212 // 使用账号密码,进行登陆。
231 213 LoginUser loginUser = this.login0(reqVO.getUsername(), reqVO.getPassword()); // TODO 芋艿:需要改造下,增加各种登陆方式
232 214 loginUser.setRoleIds(this.getUserRoleIds(loginUser.getId())); // 获取用户角色列表
233 215  
234   - // 保存社交用户
235   - this.saveSocialUser(reqVO.getType(), socialUsers, loginUser.getId(), authUser);
  216 + // 绑定社交用户(新增)
  217 + socialService.bindSocialUser(loginUser.getId(), reqVO.getType(), authUser);
236 218  
237 219 // 缓存登陆用户到 Redis 中,返回 sessionId 编号
238 220 return userSessionService.createUserSession(loginUser, userIp, userAgent);
239 221 }
240 222  
241   - private static String getAuthUserUnionId(AuthUser authUser) {
242   - return StrUtil.blankToDefault(authUser.getToken().getUnionId(), authUser.getUuid());
243   - }
244   -
245   - private AuthUser obtainAuthUserFromCache(Integer type, AuthCallback authCallback) {
246   - // 从缓存中获取
247   - AuthUser authUser = authSocialUserRedisDAO.get(type, authCallback);
248   - if (authUser != null) {
249   - return authUser;
250   - }
251   -
252   - // 请求获取
253   - authUser = this.obtainAuthUser(type, authCallback);
254   - authSocialUserRedisDAO.set(type, authCallback, authUser);
255   - return authUser;
256   - }
257   -
258   - private AuthUser obtainAuthUser(Integer type, AuthCallback authCallback) {
259   - AuthRequest authRequest = authRequestFactory.get(SysSocialTypeEnum.valueOfType(type).getSource());
260   - AuthResponse<?> authResponse = authRequest.login(authCallback);
261   - log.info("[obtainAuthUser][请求社交平台 type({}) request({}) response({})]", type, JsonUtils.toJsonString(authCallback),
262   - JsonUtils.toJsonString(authResponse));
263   - if (!authResponse.ok()) {
264   - throw exception(AUTH_THIRD_OAUTH_FAILURE, authResponse.getMsg());
265   - }
266   - return (AuthUser) authResponse.getData();
267   - }
268   -
269   - /**
270   - * 保存社交用户
271   - *
272   - * @param socialUsers 已存在的社交用户列表
273   - * @param userId 绑定的用户编号
274   - * @param authUser 需要保存的社交用户信息
275   - */
276   - private void saveSocialUser(Integer type, List<SysSocialUserDO> socialUsers, Long userId, AuthUser authUser) {
277   - // 逻辑一:如果 socialUsers 指定的 userId 改变,需要进行更新
278   - // 例如说,一个微信 unionId 对应了多个社交账号,结果其中有个关联了新的 userId,则其它也要跟着修改
279   - // 考虑到 socialUsers 一般比较少,直接 for 循环更新即可
280   - socialUsers.forEach(socialUser -> {
281   - if (Objects.equals(socialUser.getUserId(), userId)) {
282   - return;
283   - }
284   - socialUserMapper.updateById(new SysSocialUserDO().setUserId(socialUser.getUserId()).setUserId(userId));
285   - });
286   -
287   - // 逻辑二:如果 authUser 不存在于 socialUsers 中,则进行新增;否则,进行更新
288   - SysSocialUserDO saveSocialUser = CollUtil.findOneByField(socialUsers, "openid", authUser.getUuid());
289   - if (saveSocialUser == null) {
290   - saveSocialUser = new SysSocialUserDO();
291   - saveSocialUser.setUserId(userId).setUserType(UserTypeEnum.ADMIN.getValue());
292   - saveSocialUser.setType(type).setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken())
293   - .setUnionId(getAuthUserUnionId(authUser)).setRawTokenInfo(JsonUtils.toJsonString(authUser.getToken()));
294   - saveSocialUser.setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar())
295   - .setRawUserInfo(JsonUtils.toJsonString(authUser.getRawUserInfo()));
296   - socialUserMapper.insert(saveSocialUser);
297   - } else {
298   - saveSocialUser = new SysSocialUserDO().setId(saveSocialUser.getId());
299   - saveSocialUser.setToken(authUser.getToken().getAccessToken()).setUnionId(getAuthUserUnionId(authUser))
300   - .setRawTokenInfo(JsonUtils.toJsonString(authUser.getToken()));
301   - saveSocialUser.setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar())
302   - .setRawUserInfo(JsonUtils.toJsonString(authUser.getRawUserInfo()));
303   - socialUserMapper.updateById(saveSocialUser);
304   - }
305   - }
306   -
307 223 @Override
308 224 public void logout(String token) {
309 225 // 查询用户信息
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/social/SysSocialService.java 0 → 100644
  1 +package cn.iocoder.yudao.adminserver.modules.system.service.social;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social.SysSocialUserDO;
  5 +import cn.iocoder.yudao.adminserver.modules.system.enums.user.SysSocialTypeEnum;
  6 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
  7 +import me.zhyd.oauth.model.AuthUser;
  8 +
  9 +import javax.validation.constraints.NotNull;
  10 +import java.util.List;
  11 +
  12 +/**
  13 + * 社交 Service 接口,例如说社交平台的授权登录
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +public interface SysSocialService {
  18 +
  19 + /**
  20 + * 获得社交平台的授权 URL
  21 + *
  22 + * @param type 社交平台的类型 {@link SysSocialTypeEnum}
  23 + * @param redirectUri 重定向 URL
  24 + * @return 社交平台的授权 URL
  25 + */
  26 + String getAuthorizeUrl(Integer type, String redirectUri);
  27 +
  28 + /**
  29 + * 获得授权的用户
  30 + * 如果授权失败,则会抛出 {@link ServiceException} 异常
  31 + *
  32 + * @param type 社交平台的类型 {@link SysSocialTypeEnum}
  33 + * @param code 授权码
  34 + * @param state state
  35 + * @return 授权用户
  36 + */
  37 + @NotNull
  38 + AuthUser getAuthUser(Integer type, String code, String state);
  39 +
  40 + default String getAuthUserUnionId(AuthUser authUser) {
  41 + return StrUtil.blankToDefault(authUser.getToken().getUnionId(), authUser.getUuid());
  42 + }
  43 +
  44 + /**
  45 + * 获得 unionId 对应的某个社交平台的“所有”社交用户
  46 + * 注意,这里的“所有”,指的是类似【微信】平台,包括了小程序、公众号、PC 网站,他们的 unionId 是一致的
  47 + *
  48 + * @param type 社交平台的类型 {@link SysSocialTypeEnum}
  49 + * @param unionId 社交平台的 unionId
  50 + * @return 社交用户列表
  51 + */
  52 + List<SysSocialUserDO> getAllSocialUserList(Integer type, String unionId);
  53 +
  54 + /**
  55 + * 绑定社交用户
  56 + *
  57 + * @param userId 用户编号
  58 + * @param type 社交平台的类型 {@link SysSocialTypeEnum}
  59 + * @param authUser 授权用户
  60 + */
  61 + void bindSocialUser(Long userId, Integer type, AuthUser authUser);
  62 +
  63 +}
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/social/impl/SysSocialServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.adminserver.modules.system.service.social.impl;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.social.SysSocialUserDO;
  5 +import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social.SysSocialUserMapper;
  6 +import cn.iocoder.yudao.adminserver.modules.system.dal.redis.social.SysSocialAuthUserRedisDAO;
  7 +import cn.iocoder.yudao.adminserver.modules.system.enums.user.SysSocialTypeEnum;
  8 +import cn.iocoder.yudao.adminserver.modules.system.service.social.SysSocialService;
  9 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  10 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  11 +import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
  12 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  13 +import com.xkcoding.justauth.AuthRequestFactory;
  14 +import lombok.extern.slf4j.Slf4j;
  15 +import me.zhyd.oauth.model.AuthCallback;
  16 +import me.zhyd.oauth.model.AuthResponse;
  17 +import me.zhyd.oauth.model.AuthUser;
  18 +import me.zhyd.oauth.request.AuthRequest;
  19 +import me.zhyd.oauth.utils.AuthStateUtils;
  20 +import org.springframework.stereotype.Service;
  21 +
  22 +import javax.annotation.Resource;
  23 +import javax.validation.Valid;
  24 +
  25 +import java.util.List;
  26 +import java.util.Objects;
  27 +
  28 +import static cn.iocoder.yudao.adminserver.modules.system.enums.SysErrorCodeConstants.SOCIAL_AUTH_FAILURE;
  29 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  30 +
  31 +/**
  32 + * 社交 Service 实现类
  33 + *
  34 + * @author 芋道源码
  35 + */
  36 +@Service
  37 +@Valid
  38 +@Slf4j
  39 +public class SysSocialServiceImpl implements SysSocialService {
  40 +
  41 + @Resource
  42 + private AuthRequestFactory authRequestFactory;
  43 +
  44 + @Resource
  45 + private SysSocialAuthUserRedisDAO authSocialUserRedisDAO;
  46 +
  47 + @Resource
  48 + private SysSocialUserMapper socialUserMapper;
  49 +
  50 + @Override
  51 + public String getAuthorizeUrl(Integer type, String redirectUri) {
  52 + // 获得对应的 AuthRequest 实现
  53 + AuthRequest authRequest = authRequestFactory.get(SysSocialTypeEnum.valueOfType(type).getSource());
  54 + // 生成跳转地址
  55 + String authorizeUri = authRequest.authorize(AuthStateUtils.createState());
  56 + return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri);
  57 + }
  58 +
  59 + @Override
  60 + public AuthUser getAuthUser(Integer type, String code, String state) {
  61 + AuthCallback authCallback = buildAuthCallback(code, state);
  62 + // 从缓存中获取
  63 + AuthUser authUser = authSocialUserRedisDAO.get(type, authCallback);
  64 + if (authUser != null) {
  65 + return authUser;
  66 + }
  67 +
  68 + // 请求获取
  69 + authUser = this.getAuthUser0(type, authCallback);
  70 + // 缓存。原因是 code 有且可以使用一次。在社交登录时,当未绑定 User 时,需要绑定登陆,此时需要 code 使用两次
  71 + authSocialUserRedisDAO.set(type, authCallback, authUser);
  72 + return authUser;
  73 + }
  74 +
  75 + @Override
  76 + public List<SysSocialUserDO> getAllSocialUserList(Integer type, String unionId) {
  77 + List<Integer> types = SysSocialTypeEnum.getRelationTypes(type);
  78 + return socialUserMapper.selectListByTypeAndUnionId(UserTypeEnum.ADMIN.getValue(), types, unionId);
  79 + }
  80 +
  81 + @Override
  82 + public void bindSocialUser(Long userId, Integer type, AuthUser authUser) {
  83 + // 获得 unionId 对应的 SysSocialUserDO 列表
  84 + String unionId = getAuthUserUnionId(authUser);
  85 + List<SysSocialUserDO> socialUsers = this.getAllSocialUserList(type, unionId);
  86 +
  87 + // 逻辑一:如果 userId 之前绑定过该 type 的其它账号,需要进行解绑
  88 + List<Integer> types = SysSocialTypeEnum.getRelationTypes(type);
  89 + List<SysSocialUserDO> oldSocialUsers = socialUserMapper.selectListByTypeAndUserId(UserTypeEnum.ADMIN.getValue(),
  90 + types, userId);
  91 + if (CollUtil.isNotEmpty(oldSocialUsers) && !Objects.equals(unionId, oldSocialUsers.get(0).getUnionId())) {
  92 + socialUserMapper.deleteBatchIds(CollectionUtils.convertSet(oldSocialUsers, SysSocialUserDO::getId));
  93 + }
  94 +
  95 + // 逻辑二:如果 socialUsers 指定的 userId 改变,需要进行更新
  96 + // 例如说,一个微信 unionId 对应了多个社交账号,结果其中有个关联了新的 userId,则其它也要跟着修改
  97 + // 考虑到 socialUsers 一般比较少,直接 for 循环更新即可
  98 + socialUsers.forEach(socialUser -> {
  99 + if (Objects.equals(socialUser.getUserId(), userId)) {
  100 + return;
  101 + }
  102 + socialUserMapper.updateById(new SysSocialUserDO().setUserId(socialUser.getUserId()).setUserId(userId));
  103 + });
  104 +
  105 + // 逻辑三:如果 authUser 不存在于 socialUsers 中,则进行新增;否则,进行更新
  106 + SysSocialUserDO saveSocialUser = CollUtil.findOneByField(socialUsers, "openid", authUser.getUuid());
  107 + if (saveSocialUser == null) {
  108 + saveSocialUser = new SysSocialUserDO();
  109 + saveSocialUser.setUserId(userId).setUserType(UserTypeEnum.ADMIN.getValue());
  110 + saveSocialUser.setType(type).setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken())
  111 + .setUnionId(unionId).setRawTokenInfo(JsonUtils.toJsonString(authUser.getToken()));
  112 + saveSocialUser.setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar())
  113 + .setRawUserInfo(JsonUtils.toJsonString(authUser.getRawUserInfo()));
  114 + socialUserMapper.insert(saveSocialUser);
  115 + } else {
  116 + saveSocialUser = new SysSocialUserDO().setId(saveSocialUser.getId());
  117 + saveSocialUser.setToken(authUser.getToken().getAccessToken())
  118 + .setRawTokenInfo(JsonUtils.toJsonString(authUser.getToken()));
  119 + saveSocialUser.setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar())
  120 + .setRawUserInfo(JsonUtils.toJsonString(authUser.getRawUserInfo()));
  121 + socialUserMapper.updateById(saveSocialUser);
  122 + }
  123 + }
  124 +
  125 + /**
  126 + * 请求社交平台,获得授权的用户
  127 + *
  128 + * @param type 社交平台的类型
  129 + * @param authCallback 授权回调
  130 + * @return 授权的用户
  131 + */
  132 + private AuthUser getAuthUser0(Integer type, AuthCallback authCallback) {
  133 + AuthRequest authRequest = authRequestFactory.get(SysSocialTypeEnum.valueOfType(type).getSource());
  134 + AuthResponse<?> authResponse = authRequest.login(authCallback);
  135 + log.info("[getAuthUser0][请求社交平台 type({}) request({}) response({})]", type, JsonUtils.toJsonString(authCallback),
  136 + JsonUtils.toJsonString(authResponse));
  137 + if (!authResponse.ok()) {
  138 + throw exception(SOCIAL_AUTH_FAILURE, authResponse.getMsg());
  139 + }
  140 + return (AuthUser) authResponse.getData();
  141 + }
  142 +
  143 + private static AuthCallback buildAuthCallback(String code, String state) {
  144 + return AuthCallback.builder().code(code).state(state).build();
  145 + }
  146 +
  147 +}
... ...
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/user/SysUserServiceImpl.java renamed to yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/service/user/impl/SysUserServiceImpl.java
1   -package cn.iocoder.yudao.adminserver.modules.system.service.user;
  1 +package cn.iocoder.yudao.adminserver.modules.system.service.user.impl;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.io.IoUtil;
... ... @@ -12,11 +12,11 @@ import cn.iocoder.yudao.adminserver.modules.system.convert.user.SysUserConvert;
12 12 import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysDeptDO;
13 13 import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.dept.SysPostDO;
14 14 import cn.iocoder.yudao.adminserver.modules.system.dal.dataobject.user.SysUserDO;
15   -import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.social.SysUserSocialMapper;
16 15 import cn.iocoder.yudao.adminserver.modules.system.dal.mysql.user.SysUserMapper;
17 16 import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysDeptService;
18 17 import cn.iocoder.yudao.adminserver.modules.system.service.dept.SysPostService;
19 18 import cn.iocoder.yudao.adminserver.modules.system.service.permission.SysPermissionService;
  19 +import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserService;
20 20 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
21 21 import cn.iocoder.yudao.framework.common.exception.ServiceException;
22 22 import cn.iocoder.yudao.framework.common.pojo.PageResult;
... ... @@ -49,8 +49,6 @@ public class SysUserServiceImpl implements SysUserService {
49 49  
50 50 @Resource
51 51 private SysUserMapper userMapper;
52   - @Resource
53   - private SysUserSocialMapper userSocialMapper;
54 52  
55 53 @Resource
56 54 private SysDeptService deptService;
... ... @@ -218,7 +216,7 @@ public class SysUserServiceImpl implements SysUserService {
218 216 }
219 217  
220 218 @VisibleForTesting
221   - void checkUserExists(Long id) {
  219 + public void checkUserExists(Long id) {
222 220 if (id == null) {
223 221 return;
224 222 }
... ... @@ -229,7 +227,7 @@ public class SysUserServiceImpl implements SysUserService {
229 227 }
230 228  
231 229 @VisibleForTesting
232   - void checkUsernameUnique(Long id, String username) {
  230 + public void checkUsernameUnique(Long id, String username) {
233 231 if (StrUtil.isBlank(username)) {
234 232 return;
235 233 }
... ... @@ -247,7 +245,7 @@ public class SysUserServiceImpl implements SysUserService {
247 245 }
248 246  
249 247 @VisibleForTesting
250   - void checkEmailUnique(Long id, String email) {
  248 + public void checkEmailUnique(Long id, String email) {
251 249 if (StrUtil.isBlank(email)) {
252 250 return;
253 251 }
... ... @@ -265,7 +263,7 @@ public class SysUserServiceImpl implements SysUserService {
265 263 }
266 264  
267 265 @VisibleForTesting
268   - void checkMobileUnique(Long id, String mobile) {
  266 + public void checkMobileUnique(Long id, String mobile) {
269 267 if (StrUtil.isBlank(mobile)) {
270 268 return;
271 269 }
... ... @@ -283,7 +281,7 @@ public class SysUserServiceImpl implements SysUserService {
283 281 }
284 282  
285 283 @VisibleForTesting
286   - void checkDeptEnable(Long deptId) {
  284 + public void checkDeptEnable(Long deptId) {
287 285 if (deptId == null) { // 允许不选择
288 286 return;
289 287 }
... ... @@ -297,7 +295,7 @@ public class SysUserServiceImpl implements SysUserService {
297 295 }
298 296  
299 297 @VisibleForTesting
300   - void checkPostEnable(Set<Long> postIds) {
  298 + public void checkPostEnable(Set<Long> postIds) {
301 299 if (CollUtil.isEmpty(postIds)) { // 允许不选择
302 300 return;
303 301 }
... ... @@ -324,7 +322,7 @@ public class SysUserServiceImpl implements SysUserService {
324 322 * @param oldPassword 旧密码
325 323 */
326 324 @VisibleForTesting
327   - void checkOldPassword(Long id, String oldPassword) {
  325 + public void checkOldPassword(Long id, String oldPassword) {
328 326 SysUserDO user = userMapper.selectById(id);
329 327 if (user == null) {
330 328 throw exception(USER_NOT_EXISTS);
... ...
yudao-admin-server/src/main/resources/application-local.yaml
... ... @@ -185,4 +185,6 @@ justauth:
185 185 agent-id: 1000004
186 186 ignore-check-redirect-uri: true
187 187 cache:
188   - type: default
  188 + type: REDIS
  189 + prefix: 'social_auth_state:' # 缓存前缀,目前只对 Redis 缓存生效,默认 JUSTAUTH::STATE::
  190 + timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟
... ...
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/auth/SysUserSessionServiceImplTest.java
... ... @@ -41,7 +41,7 @@ import cn.iocoder.yudao.adminserver.modules.system.enums.common.SysSexEnum;
41 41 import cn.iocoder.yudao.adminserver.modules.system.service.auth.impl.SysUserSessionServiceImpl;
42 42 import cn.iocoder.yudao.adminserver.modules.system.service.dept.impl.SysDeptServiceImpl;
43 43 import cn.iocoder.yudao.adminserver.modules.system.service.logger.impl.SysLoginLogServiceImpl;
44   -import cn.iocoder.yudao.adminserver.modules.system.service.user.SysUserServiceImpl;
  44 +import cn.iocoder.yudao.adminserver.modules.system.service.user.impl.SysUserServiceImpl;
45 45 import cn.iocoder.yudao.framework.test.core.util.AssertUtils;
46 46 import cn.iocoder.yudao.framework.test.core.util.RandomUtils;
47 47 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
... ...
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/system/service/user/SysUserServiceImplTest.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.adminserver.modules.system.service.user;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.util.RandomUtil;
5 5 import cn.iocoder.yudao.adminserver.BaseDbUnitTest;
  6 +import cn.iocoder.yudao.adminserver.modules.system.service.user.impl.SysUserServiceImpl;
6 7 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 9 import cn.iocoder.yudao.adminserver.modules.infra.service.file.InfFileService;
... ...