拓展授权登录抽取成单独的starter,拓展配置和默认配置齐平

This commit is contained in:
timfruit
2021-10-31 13:09:55 +08:00
parent 8dbd6143bf
commit 8aa45406fd
23 changed files with 288 additions and 99 deletions

View File

@ -1,33 +0,0 @@
package cn.iocoder.yudao.coreservice.modules.system.compent.justauth;
import me.zhyd.oauth.config.AuthSource;
public enum AuthExtendSource implements AuthSource {
/**
* 微信小程序授权登录
*/
WECHAT_MINI_PROGRAM{
@Override
public String authorize() {
// https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
throw new UnsupportedOperationException("不支持获取授权url, 请使用小程序内置函数wx.login()登录获取code");
}
@Override
public String accessToken() {
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
// 获取openid, unionid , session_key
return "https://api.weixin.qq.com/sns/jscode2session";
}
@Override
public String userInfo() {
//https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html
throw new UnsupportedOperationException("不支持获取用户信息url, 请使用小程序内置函数wx.getUserProfile()获取用户信息");
}
}
;
}

View File

@ -1,22 +0,0 @@
package cn.iocoder.yudao.coreservice.modules.system.compent.justauth;
import lombok.*;
import me.zhyd.oauth.model.AuthToken;
/**
* TODO @timfruit类注释
* @author timfruit
* @date 2021-10-29
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class AuthExtendToken extends AuthToken {
/**
* 微信小程序 会话密钥
*/
private String miniSessionKey;
}

View File

@ -1,90 +0,0 @@
package cn.iocoder.yudao.coreservice.modules.system.compent.justauth;
import com.alibaba.fastjson.JSONObject;
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;
// TODO @timfruit新建一个 yudao-spring-boot-starter-biz-social 包,把这个拓展拿进去哈。另外,可以思考下。
// 1. application-local.yaml 的配置里justauth.extend.enum-class 能否不配置,而是自动配置好
// 2. application-local.yaml 的配置里justauth.extend.extend.config.WECHAT_MINI_PROGRAM 有办法和 justauth.type.WECHAT_MP 持平
/**
* 微信小程序登陆
*
* @author timfruit
* @date 2021-10-29
*/
public class AuthWeChatMiniProgramRequest extends AuthDefaultRequest {
public AuthWeChatMiniProgramRequest(AuthConfig config) {
super(config, AuthExtendSource.WECHAT_MINI_PROGRAM);
}
public AuthWeChatMiniProgramRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config, AuthExtendSource.WECHAT_MINI_PROGRAM, authStateCache);
}
@Override
protected AuthToken getAccessToken(AuthCallback authCallback) {
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
String response = new HttpUtils(config.getHttpConfig()).get(accessTokenUrl(authCallback.getCode()));
JSONObject accessTokenObject = JSONObject.parseObject(response); // TODO @timfruit使用 JsonUtils项目尽量避免直接使用某个 json 库
this.checkResponse(accessTokenObject);
AuthExtendToken token = new AuthExtendToken();
token.setMiniSessionKey(accessTokenObject.getString("session_key"));
token.setOpenId(accessTokenObject.getString("openid"));
token.setUnionId(accessTokenObject.getString("unionid"));
return token;
}
@Override
protected AuthUser getUserInfo(AuthToken authToken) {
// https://developers.weixin.qq.com/miniprogram/dev/api/open-api/user-info/wx.getUserProfile.html
// 如果需要用户信息,需要在小程序调用函数后传给后端
return AuthUser.builder()
.uuid(authToken.getOpenId())
//TODO 是使用默认值,还是有小程序获取用户信息 和 code 一起传过来
.nickname("")
.avatar("")
.token(authToken)
.source(source.toString())
.build();
}
/**
* 检查响应内容是否正确
*
* @param object 请求响应内容
*/
private void checkResponse(JSONObject object) {
int code = object.getIntValue("errcode");
if(code != 0){ // TODO @timfruitif (code != 0) { ,注意空格
throw new AuthException(object.getIntValue("errcode"), object.getString("errmsg"));
}
}
/**
* 返回获取 accessToken 的 url
*
* @param code 授权码
* @return 返回获取 accessToken 的 url
*/
@Override
protected String accessTokenUrl(String code) {
return UrlBuilder.fromBaseUrl(source.accessToken())
.queryParam("appid", config.getClientId())
.queryParam("secret", config.getClientSecret())
.queryParam("js_code", code)
.queryParam("grant_type", "authorization_code")
.build();
}
}

View File

@ -8,9 +8,8 @@ import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
// TODO @timfruitSysSocialUserCoreMapper 改名方便区分
@Mapper
public interface SysSocialUserMapper extends BaseMapperX<SysSocialUserDO> {
public interface SysSocialUserCoreMapper extends BaseMapperX<SysSocialUserDO> {
default List<SysSocialUserDO> selectListByTypeAndUnionId(Integer userType, Collection<Integer> types, String unionId) {
return selectList(new QueryWrapper<SysSocialUserDO>().eq("user_type", userType)

View File

@ -10,9 +10,8 @@ import javax.annotation.Resource;
import static cn.iocoder.yudao.coreservice.modules.system.dal.redis.SysRedisKeyCoreConstants.SOCIAL_AUTH_USER;
// TODO @timfruit这里的 AuthUser 还是保留全路径,主要想体现出来,不是自己定义的
/**
* 社交 {@link AuthUser} 的 RedisDAO
* 社交 {@link me.zhyd.oauth.model.AuthUser} 的 RedisDAO
*
* @author 芋道源码
*/

View File

@ -15,8 +15,7 @@ import java.util.List;
*
* @author 芋道源码
*/
// TODO @timfruitSysSocialCoreService 改名方便区分
public interface SysSocialService {
public interface SysSocialCoreService {
/**
* 获得社交平台的授权 URL
@ -50,6 +49,7 @@ public interface SysSocialService {
* @param type 社交平台的类型 {@link SysSocialTypeEnum}
* @param unionId 社交平台的 unionId
* @return 社交用户列表
* @param userTypeEnum 全局用户类型
*/
List<SysSocialUserDO> getAllSocialUserList(Integer type, String unionId, UserTypeEnum userTypeEnum);
@ -58,6 +58,7 @@ public interface SysSocialService {
*
* @param userId 用户编号
* @return 社交用户列表
* @param userTypeEnum 全局用户类型
*/
List<SysSocialUserDO> getSocialUserList(Long userId, UserTypeEnum userTypeEnum);
@ -67,6 +68,7 @@ public interface SysSocialService {
* @param userId 用户编号
* @param type 社交平台的类型 {@link SysSocialTypeEnum}
* @param authUser 授权用户
* @param userTypeEnum 全局用户类型
*/
void bindSocialUser(Long userId, Integer type, AuthUser authUser, UserTypeEnum userTypeEnum);
@ -76,8 +78,8 @@ public interface SysSocialService {
* @param userId 用户编号
* @param type 社交平台的类型 {@link SysSocialTypeEnum}
* @param unionId 社交平台的 unionId
* @param userTypeEnum 全局用户类型
*/
void unbindSocialUser(Long userId, Integer type, String unionId,UserTypeEnum userTypeEnum);
// TODO @timfruit逗号后面要有空格缺少了 @userTypeEnum 的注释都补充下哈
void unbindSocialUser(Long userId, Integer type, String unionId, UserTypeEnum userTypeEnum);
}

View File

@ -2,15 +2,15 @@ package cn.iocoder.yudao.coreservice.modules.system.service.social.impl;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO;
import cn.iocoder.yudao.coreservice.modules.system.dal.mysql.social.SysSocialUserMapper;
import cn.iocoder.yudao.coreservice.modules.system.dal.mysql.social.SysSocialUserCoreMapper;
import cn.iocoder.yudao.coreservice.modules.system.dal.redis.social.SysSocialAuthUserRedisDAO;
import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialService;
import cn.iocoder.yudao.coreservice.modules.system.service.social.SysSocialCoreService;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.framework.social.core.YudaoAuthRequestFactory;
import com.google.common.annotations.VisibleForTesting;
import com.xkcoding.justauth.AuthRequestFactory;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.model.AuthResponse;
@ -38,21 +38,21 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
@Service
@Validated
@Slf4j
public class SysSocialServiceImpl implements SysSocialService {
public class SysSocialCoreServiceImpl implements SysSocialCoreService {
@Resource
private AuthRequestFactory authRequestFactory;
private YudaoAuthRequestFactory yudaoAuthRequestFactory;
@Resource
private SysSocialAuthUserRedisDAO authSocialUserRedisDAO;
@Resource
private SysSocialUserMapper socialUserMapper;
private SysSocialUserCoreMapper socialUserMapper;
@Override
public String getAuthorizeUrl(Integer type, String redirectUri) {
// 获得对应的 AuthRequest 实现
AuthRequest authRequest = authRequestFactory.get(SysSocialTypeEnum.valueOfType(type).getSource());
AuthRequest authRequest = yudaoAuthRequestFactory.get(SysSocialTypeEnum.valueOfType(type).getSource());
// 生成跳转地址
String authorizeUri = authRequest.authorize(AuthStateUtils.createState());
return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri);
@ -161,7 +161,7 @@ public class SysSocialServiceImpl implements SysSocialService {
* @return 授权的用户
*/
private AuthUser getAuthUser0(Integer type, AuthCallback authCallback) {
AuthRequest authRequest = authRequestFactory.get(SysSocialTypeEnum.valueOfType(type).getSource());
AuthRequest authRequest = yudaoAuthRequestFactory.get(SysSocialTypeEnum.valueOfType(type).getSource());
AuthResponse<?> authResponse = authRequest.login(authCallback);
log.info("[getAuthUser0][请求社交平台 type({}) request({}) response({})]", type, toJsonString(authCallback),
toJsonString(authResponse));

View File

@ -0,0 +1,171 @@
package cn.iocoder.yudao.coreservice.modules.system.service.social;
import cn.iocoder.yudao.coreservice.BaseDbAndRedisUnitTest;
import cn.iocoder.yudao.coreservice.modules.system.dal.dataobject.social.SysSocialUserDO;
import cn.iocoder.yudao.coreservice.modules.system.dal.mysql.social.SysSocialUserCoreMapper;
import cn.iocoder.yudao.coreservice.modules.system.dal.redis.social.SysSocialAuthUserRedisDAO;
import cn.iocoder.yudao.coreservice.modules.system.enums.social.SysSocialTypeEnum;
import cn.iocoder.yudao.coreservice.modules.system.service.social.impl.SysSocialCoreServiceImpl;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import com.xkcoding.justauth.AuthRequestFactory;
import me.zhyd.oauth.model.AuthUser;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.List;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.hutool.core.util.RandomUtil.randomString;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* {@link SysSocialCoreServiceImpl} 的单元测试类
*
* @author 芋道源码
*/
@Import({SysSocialCoreServiceImpl.class, SysSocialAuthUserRedisDAO.class})
public class SysSocialCoreServiceTest extends BaseDbAndRedisUnitTest {
@Resource
private SysSocialCoreServiceImpl socialService;
@Resource
private SysSocialUserCoreMapper socialUserMapper;
@MockBean
private AuthRequestFactory authRequestFactory;
/**
* 情况一,创建 SysSocialUserDO 的情况
*/
@Test
public void testBindSocialUser_create() {
// mock 数据
// 准备参数
Long userId = randomLongId();
Integer type = randomEle(SysSocialTypeEnum.values()).getType();
AuthUser authUser = randomPojo(AuthUser.class);
// mock 方法
// 调用
socialService.bindSocialUser(userId, type, authUser, UserTypeEnum.ADMIN);
// 断言
List<SysSocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
assertEquals(1, socialUsers.size());
assertBindSocialUser(socialUsers.get(0), authUser, userId, type);
}
/**
* 情况二,更新 SysSocialUserDO 的情况
*/
@Test
public void testBindSocialUser_update() {
// mock 数据
SysSocialUserDO dbSocialUser = randomPojo(SysSocialUserDO.class, socialUserDO -> {
socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
socialUserDO.setType(randomEle(SysSocialTypeEnum.values()).getType());
});
socialUserMapper.insert(dbSocialUser);
// 准备参数
Long userId = dbSocialUser.getUserId();
Integer type = dbSocialUser.getType();
AuthUser authUser = randomPojo(AuthUser.class);
// mock 方法
// 调用
socialService.bindSocialUser(userId, type, authUser, UserTypeEnum.ADMIN);
// 断言
List<SysSocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
assertEquals(1, socialUsers.size());
assertBindSocialUser(socialUsers.get(0), authUser, userId, type);
}
/**
* 情况一和二都存在的,逻辑二的场景
*/
@Test
public void testBindSocialUser_userId() {
// mock 数据
SysSocialUserDO dbSocialUser = randomPojo(SysSocialUserDO.class, socialUserDO -> {
socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
socialUserDO.setType(randomEle(SysSocialTypeEnum.values()).getType());
});
socialUserMapper.insert(dbSocialUser);
// 准备参数
Long userId = randomLongId();
Integer type = dbSocialUser.getType();
AuthUser authUser = randomPojo(AuthUser.class);
// mock 方法
// 调用
socialService.bindSocialUser(userId, type, authUser, UserTypeEnum.ADMIN);
// 断言
List<SysSocialUserDO> socialUsers = socialUserMapper.selectList("user_id", userId);
assertEquals(1, socialUsers.size());
}
private void assertBindSocialUser(SysSocialUserDO socialUser, AuthUser authUser, Long userId,
Integer type) {
assertEquals(authUser.getToken().getAccessToken(), socialUser.getToken());
assertEquals(toJsonString(authUser.getToken()), socialUser.getRawTokenInfo());
assertEquals(authUser.getNickname(), socialUser.getNickname());
assertEquals(authUser.getAvatar(), socialUser.getAvatar());
assertEquals(toJsonString(authUser.getRawUserInfo()), socialUser.getRawUserInfo());
assertEquals(userId, socialUser.getUserId());
assertEquals(UserTypeEnum.ADMIN.getValue(), socialUser.getUserType());
assertEquals(type, socialUser.getType());
assertEquals(authUser.getUuid(), socialUser.getOpenid());
assertEquals(socialService.getAuthUserUnionId(authUser), socialUser.getUnionId());
}
/**
* 情况一,如果新老的 unionId 是一致的,无需解绑
*/
@Test
public void testUnbindOldSocialUser_no() {
// mock 数据
SysSocialUserDO oldSocialUser = randomPojo(SysSocialUserDO.class, socialUserDO -> {
socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
socialUserDO.setType(randomEle(SysSocialTypeEnum.values()).getType());
});
socialUserMapper.insert(oldSocialUser);
// 准备参数
Long userId = oldSocialUser.getUserId();
Integer type = oldSocialUser.getType();
String newUnionId = oldSocialUser.getUnionId();
// 调用
socialService.unbindOldSocialUser(userId, type, newUnionId, UserTypeEnum.ADMIN);
// 断言
assertEquals(1L, socialUserMapper.selectCount(null).longValue());
}
/**
* 情况二,如果新老的 unionId 不一致的,需解绑
*/
@Test
public void testUnbindOldSocialUser_yes() {
// mock 数据
SysSocialUserDO oldSocialUser = randomPojo(SysSocialUserDO.class, socialUserDO -> {
socialUserDO.setUserType(UserTypeEnum.ADMIN.getValue());
socialUserDO.setType(randomEle(SysSocialTypeEnum.values()).getType());
});
socialUserMapper.insert(oldSocialUser);
// 准备参数
Long userId = oldSocialUser.getUserId();
Integer type = oldSocialUser.getType();
String newUnionId = randomString(10);
// 调用
socialService.unbindOldSocialUser(userId, type, newUnionId, UserTypeEnum.ADMIN);
// 断言
assertEquals(0L, socialUserMapper.selectCount(null).longValue());
}
}