登录:社交登录时,额外返回 openid

This commit is contained in:
YunaiV 2023-09-10 18:01:53 +08:00
parent 4265441e1d
commit 40e1a879e3
13 changed files with 111 additions and 46 deletions

View File

@ -28,6 +28,17 @@ tenant-id: {{appTenentId}}
"code": 9999
}
### 请求 /social-login 接口 => 成功
POST {{appApi}}/member/auth/social-login
Content-Type: application/json
tenant-id: {{appTenentId}}
{
"type": 34,
"code": "0e1oc9000CTjFQ1oim200bhtb61oc90g",
"state": "default"
}
### 请求 /weixin-mini-app-login 接口 => 成功
POST {{appApi}}/member/auth/weixin-mini-app-login
Content-Type: application/json
@ -38,7 +49,6 @@ tenant-id: {{appTenentId}}
"loginCode": "001frTkl21JUf94VGxol2hSlff1frTkR"
}
### 请求 /logout 接口 => 成功
POST {{appApi}}/member/auth/logout
Content-Type: application/json

View File

@ -27,4 +27,12 @@ public class AppAuthLoginRespVO {
@Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime expiresTime;
/**
* 仅社交登录社交绑定时会返回
*
* 为什么需要返回微信公众号微信小程序支付需要传递 openid 给支付接口
*/
@Schema(description = "社交用户 openid", example = "qq768")
private String openid;
}

View File

@ -25,7 +25,7 @@ public interface AuthConvert {
SmsCodeUseReqDTO convert(AppMemberUserResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);
SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);
AppAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean);
AppAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean, String openid);
SmsCodeValidateReqDTO convert(AppAuthSmsValidateReqVO bean);

View File

@ -20,6 +20,7 @@ import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants;
@ -65,13 +66,14 @@ public class MemberAuthServiceImpl implements MemberAuthService {
MemberUserDO user = login0(reqVO.getMobile(), reqVO.getPassword());
// 如果 socialType 非空说明需要绑定社交用户
String openid = null;
if (reqVO.getSocialType() != null) {
socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
}
// 创建 Token 令牌记录登录日志
return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);
return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE, openid);
}
@Override
@ -86,32 +88,33 @@ public class MemberAuthServiceImpl implements MemberAuthService {
Assert.notNull(user, "获取用户失败,结果为空");
// 如果 socialType 非空说明需要绑定社交用户
String openid = null;
if (reqVO.getSocialType() != null) {
socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
}
// 创建 Token 令牌记录登录日志
return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS);
return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS, openid);
}
@Override
public AppAuthLoginRespVO socialLogin(AppAuthSocialLoginReqVO reqVO) {
// 使用 code 授权码进行登录然后获得到绑定的用户编号
Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
SocialUserRespDTO socialUser = socialUserApi.getSocialUser(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),
reqVO.getCode(), reqVO.getState());
if (userId == null) {
if (socialUser == null) {
throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
}
// 自动登录
MemberUserDO user = userService.getUser(userId);
MemberUserDO user = userService.getUser(socialUser.getUserId());
if (user == null) {
throw exception(USER_NOT_EXISTS);
}
// 创建 Token 令牌记录登录日志
return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL);
return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL, socialUser.getOpenid());
}
@Override
@ -129,14 +132,15 @@ public class MemberAuthServiceImpl implements MemberAuthService {
Assert.notNull(user, "获取用户失败,结果为空");
// 绑定社交用户
socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
String openid = socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
SocialTypeEnum.WECHAT_MINI_APP.getType(), reqVO.getLoginCode(), ""));
// 创建 Token 令牌记录登录日志
return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL);
return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL, openid);
}
private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile, LoginLogTypeEnum logType) {
private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile,
LoginLogTypeEnum logType, String openid) {
// 插入登陆日志
createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS);
// 创建 Token 令牌
@ -144,7 +148,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
.setUserId(user.getId()).setUserType(getUserType().getValue())
.setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT));
// 构建返回结果
return AuthConvert.INSTANCE.convert(accessTokenRespDTO);
return AuthConvert.INSTANCE.convert(accessTokenRespDTO, openid);
}
@Override
@ -231,7 +235,7 @@ public class MemberAuthServiceImpl implements MemberAuthService {
public AppAuthLoginRespVO refreshToken(String refreshToken) {
OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken,
OAuth2ClientConstants.CLIENT_ID_DEFAULT);
return AuthConvert.INSTANCE.convert(accessTokenDO);
return AuthConvert.INSTANCE.convert(accessTokenDO, null);
}
private void createLogoutLog(Long userId) {

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.api.social;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
@ -27,8 +28,9 @@ public interface SocialUserApi {
* 绑定社交用户
*
* @param reqDTO 绑定信息
* @return 社交用户 openid
*/
void bindSocialUser(@Valid SocialUserBindReqDTO reqDTO);
String bindSocialUser(@Valid SocialUserBindReqDTO reqDTO);
/**
* 取消绑定社交用户
@ -38,16 +40,17 @@ public interface SocialUserApi {
void unbindSocialUser(@Valid SocialUserUnbindReqDTO reqDTO);
/**
* 获得社交用户的绑定用户编号
* 注意返回的是 MemberUser 或者 AdminUser id 编号
* 获得社交用户
*
* 在认证信息不正确的情况下也会抛出 {@link ServiceException} 业务异常
*
* @param userType 用户类型
* @param type 社交平台的类型
* @param code 授权码
* @param state state
* @return 绑定用户编号
* @return 社交用户
*/
Long getBindUserId(Integer userType, Integer type, String code, String state);
SocialUserRespDTO getSocialUser(Integer userType, Integer type,
String code, String state);
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.system.api.social.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 社交用户 Response DTO
*
* @author 芋道源码
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SocialUserRespDTO {
/**
* 社交用户 openid
*/
private String openid;
/**
* 关联的用户编号
*/
private Long userId;
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.api.social;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO;
import cn.iocoder.yudao.module.system.service.social.SocialUserService;
import org.springframework.stereotype.Service;
@ -26,8 +27,8 @@ public class SocialUserApiImpl implements SocialUserApi {
}
@Override
public void bindSocialUser(SocialUserBindReqDTO reqDTO) {
socialUserService.bindSocialUser(reqDTO);
public String bindSocialUser(SocialUserBindReqDTO reqDTO) {
return socialUserService.bindSocialUser(reqDTO);
}
@Override
@ -37,8 +38,8 @@ public class SocialUserApiImpl implements SocialUserApi {
}
@Override
public Long getBindUserId(Integer userType, Integer type, String code, String state) {
return socialUserService.getBindUserId(userType, type, code, state);
public SocialUserRespDTO getSocialUser(Integer userType, Integer type, String code, String state) {
return socialUserService.getSocialUser(userType, type, code, state);
}
}

View File

@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
@ -155,14 +156,14 @@ public class AdminAuthServiceImpl implements AdminAuthService {
@Override
public AuthLoginRespVO socialLogin(AuthSocialLoginReqVO reqVO) {
// 使用 code 授权码进行登录然后获得到绑定的用户编号
Long userId = socialUserService.getBindUserId(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),
SocialUserRespDTO socialUser = socialUserService.getSocialUser(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),
reqVO.getCode(), reqVO.getState());
if (userId == null) {
if (socialUser == null) {
throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
}
// 获得用户
AdminUserDO user = userService.getUser(userId);
AdminUserDO user = userService.getUser(socialUser.getUserId());
if (user == null) {
throw exception(USER_NOT_EXISTS);
}

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.service.social;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
@ -50,8 +51,9 @@ public interface SocialUserService {
* 绑定社交用户
*
* @param reqDTO 绑定信息
* @return 社交用户 openid
*/
void bindSocialUser(@Valid SocialUserBindReqDTO reqDTO);
String bindSocialUser(@Valid SocialUserBindReqDTO reqDTO);
/**
* 取消绑定社交用户
@ -64,15 +66,16 @@ public interface SocialUserService {
void unbindSocialUser(Long userId, Integer userType, Integer type, String openid);
/**
* 获得社交用户的绑定用户编号
* 注意返回的是 MemberUser 或者 AdminUser id 编号
* 获得社交用户
*
* 在认证信息不正确的情况下也会抛出 {@link ServiceException} 业务异常
*
* @param userType 用户类型
* @param type 社交平台的类型
* @param code 授权码
* @param state state
* @return 绑定用户编号
* @return 社交用户
*/
Long getBindUserId(Integer userType, Integer type, String code, String state);
SocialUserRespDTO getSocialUser(Integer userType, Integer type, String code, String state);
}

View File

@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
@ -98,7 +99,7 @@ public class SocialUserServiceImpl implements SocialUserService {
@Override
@Transactional
public void bindSocialUser(SocialUserBindReqDTO reqDTO) {
public String bindSocialUser(SocialUserBindReqDTO reqDTO) {
// 获得社交用户
SocialUserDO socialUser = authSocialUser(reqDTO.getType(), reqDTO.getCode(), reqDTO.getState());
Assert.notNull(socialUser, "社交用户不能为空");
@ -115,6 +116,7 @@ public class SocialUserServiceImpl implements SocialUserService {
.userId(reqDTO.getUserId()).userType(reqDTO.getUserType())
.socialUserId(socialUser.getId()).socialType(socialUser.getType()).build();
socialUserBindMapper.insert(socialUserBind);
return socialUser.getOpenid();
}
@Override
@ -130,7 +132,7 @@ public class SocialUserServiceImpl implements SocialUserService {
}
@Override
public Long getBindUserId(Integer userType, Integer type, String code, String state) {
public SocialUserRespDTO getSocialUser(Integer userType, Integer type, String code, String state) {
// 获得社交用户
SocialUserDO socialUser = authSocialUser(type, code, state);
Assert.notNull(socialUser, "社交用户不能为空");
@ -141,7 +143,7 @@ public class SocialUserServiceImpl implements SocialUserService {
if (socialUserBind == null) {
throw exception(AUTH_THIRD_LOGIN_NOT_BIND);
}
return socialUserBind.getUserId();
return new SocialUserRespDTO(socialUser.getOpenid(), socialUserBind.getUserId());
}
/**

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
@ -235,8 +236,8 @@ public class AdminAuthServiceImplTest extends BaseDbUnitTest {
AuthSocialLoginReqVO reqVO = randomPojo(AuthSocialLoginReqVO.class);
// mock 方法绑定的用户编号
Long userId = 1L;
when(socialUserService.getBindUserId(eq(UserTypeEnum.ADMIN.getValue()), eq(reqVO.getType()),
eq(reqVO.getCode()), eq(reqVO.getState()))).thenReturn(userId);
when(socialUserService.getSocialUser(eq(UserTypeEnum.ADMIN.getValue()), eq(reqVO.getType()),
eq(reqVO.getCode()), eq(reqVO.getState()))).thenReturn(new SocialUserRespDTO(randomString(), userId));
// mock用户
AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(userId));
when(userService.getUser(eq(userId))).thenReturn(user);

View File

@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO;
import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.social.SocialUserBindMapper;
@ -195,10 +196,11 @@ public class SocialUserServiceImplTest extends BaseDbUnitTest {
.setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(socialUser.getId()));
// 调用
socialUserService.bindSocialUser(reqDTO);
String openid = socialUserService.bindSocialUser(reqDTO);
// 断言
List<SocialUserBindDO> socialUserBinds = socialUserBindMapper.selectList();
assertEquals(1, socialUserBinds.size());
assertEquals(socialUser.getOpenid(), openid);
}
@Test
@ -232,25 +234,26 @@ public class SocialUserServiceImplTest extends BaseDbUnitTest {
}
@Test
public void testGetBindUserId() {
public void testGetSocialUser() {
// 准备参数
Integer userType = UserTypeEnum.ADMIN.getValue();
Integer type = SocialTypeEnum.GITEE.getType();
String code = "tudou";
String state = "yuanma";
// mock 社交用户
SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setCode(code).setState(state);
socialUserMapper.insert(socialUser);
SocialUserDO socialUserDO = randomPojo(SocialUserDO.class).setType(type).setCode(code).setState(state);
socialUserMapper.insert(socialUserDO);
// mock 社交用户的绑定
Long userId = randomLong();
SocialUserBindDO socialUserBind = randomPojo(SocialUserBindDO.class).setUserType(userType).setUserId(userId)
.setSocialType(type).setSocialUserId(socialUser.getId());
.setSocialType(type).setSocialUserId(socialUserDO.getId());
socialUserBindMapper.insert(socialUserBind);
// 调用
Long result = socialUserService.getBindUserId(userType, type, code, state);
SocialUserRespDTO socialUser = socialUserService.getSocialUser(userType, type, code, state);
// 断言
assertEquals(userId, result);
assertEquals(userId, socialUser.getUserId());
assertEquals(socialUserDO.getOpenid(), socialUser.getOpenid());
}
}

View File

@ -173,8 +173,10 @@ wx:
key-prefix: wx # Redis Key 的前缀
http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台
miniapp: # 小程序配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档
appid: wx62056c0d5e8db250
secret: 333ae72f41552af1e998fe1f54e1584a
# appid: wx62056c0d5e8db250
# secret: 333ae72f41552af1e998fe1f54e1584a
appid: wx63c280fe3248a3e7 # wenhualian的接口测试号
secret: 6f270509224a7ae1296bbf1c8cb97aed
config-storage:
type: RedisTemplate # 采用 RedisTemplate 操作 Redis会自动从 Spring 中获取
key-prefix: wa # Redis Key 的前缀