【升级】嵌入模式完成登录交互,可以将本系统嵌入到三方系统内

This commit is contained in:
俞宝山
2026-02-11 18:18:01 +08:00
parent 3d6743d9a8
commit 7ccbeb8cf6
9 changed files with 238 additions and 4 deletions

View File

@@ -182,13 +182,26 @@ public class AuthController {
return CommonResult.data(authService.doLoginByOtp(authOtpLoginParam, SaClientTypeEnum.B.getValue()));
}
/**
* B端第三方Token交换登录用于iframe嵌入免登
*
* @author yubaoshan
* @date 2026/2/11
**/
@ApiOperationSupport(order = 11)
@Operation(summary = "B端第三方Token交换登录iframe嵌入免登")
@PostMapping("/auth/b/doLoginByThirdToken")
public CommonResult<String> doLoginByThirdToken(@RequestBody @Valid AuthThirdTokenLoginParam authThirdTokenLoginParam) {
return CommonResult.data(authService.doLoginByThirdToken(authThirdTokenLoginParam));
}
/**
* B端判断是否登录
*
* @author xuyuxiang
* @date 2021/10/15 13:12
**/
@ApiOperationSupport(order = 11)
@ApiOperationSupport(order = 12)
@Operation(summary = "B端判断是否登录")
@GetMapping("/auth/b/isLogin")
public CommonResult<Boolean> isLogin() {

View File

@@ -0,0 +1,34 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package vip.xiaonuo.auth.modular.login.param;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;
/**
* 第三方Token交换登录参数用于iframe嵌入免登
*
* @author yubaoshan
* @date 2026/2/11
**/
@Getter
@Setter
public class AuthThirdTokenLoginParam {
/** 第三方系统的accessToken */
@Schema(description = "第三方系统的accessToken", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "accessToken不能为空")
private String accessToken;
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright [2022] [https://www.xiaonuo.vip]
*
* Snowy采用APACHE LICENSE 2.0开源协议,您在使用过程中,需要注意以下几点:
*
* 1.请不要删除和修改根目录下的LICENSE文件。
* 2.请不要删除和修改Snowy源码头部的版权声明。
* 3.本项目代码可免费商业使用,商业使用请保留源码和相关描述文件的项目出处,作者声明等。
* 4.分发源码时候,请注明软件出处 https://www.xiaonuo.vip
* 5.不可二次分发开源参与同类竞品如有想法可联系团队xiaonuobase@qq.com商议合作。
* 6.若您的项目无法满足以上几点需要更多功能代码获取Snowy商业授权许可请在官网购买授权地址为 https://www.xiaonuo.vip
*/
package vip.xiaonuo.auth.modular.login.prop;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 第三方系统Token交换配置属性用于iframe嵌入免登
*
* 对接规范甲方提供一个用户信息接口GET请求Header传 Authorization: Bearer {accessToken}
* 必须返回如下JSON格式
* {
* "code": 200,
* "data": {
* "account": "zhangsan",
* "email": "zhangsan@xxx.com",
* "phone": "13800138000"
* }
* }
*
* @author yubaoshan
* @date 2026/2/11
**/
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "snowy.third-auth")
public class AuthThirdClientProperties {
/** 第三方用户信息接口地址(甲方提供),配置了即代表开启,不配置即关闭 */
private String userInfoUrl;
}

View File

@@ -145,6 +145,16 @@ public interface AuthService {
**/
void validValidCode(String phoneOrEmail, String validCode, String validCodeReqNo);
/**
* 通过第三方Token交换登录用于iframe嵌入免登
*
* @param authThirdTokenLoginParam 第三方Token登录参数
* @return 本系统Token
* @author yubaoshan
* @date 2026/2/11
*/
String doLoginByThirdToken(AuthThirdTokenLoginParam authThirdTokenLoginParam);
/**
* 获取B端验证码是否开启
*

View File

@@ -22,6 +22,8 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.PhoneUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
@@ -54,6 +56,7 @@ import vip.xiaonuo.dev.api.DevConfigApi;
import vip.xiaonuo.dev.api.DevEmailApi;
import vip.xiaonuo.dev.api.DevSmsApi;
import vip.xiaonuo.sys.api.SysUserApi;
import vip.xiaonuo.auth.modular.login.prop.AuthThirdClientProperties;
import java.util.List;
import java.util.stream.Collectors;
@@ -175,6 +178,9 @@ public class AuthServiceImpl implements AuthService {
@Resource
private ClientUserApi clientUserApi;
@Resource
private AuthThirdClientProperties authThirdClientProperties;
@Override
public AuthPicValidCodeResult getPicCaptcha(String type) {
// 生成验证码随机4位字符
@@ -1178,6 +1184,92 @@ public class AuthServiceImpl implements AuthService {
}
}
@Override
public String doLoginByThirdToken(AuthThirdTokenLoginParam authThirdTokenLoginParam) {
// 校验是否配置了第三方用户信息接口地址(配置了即开启)
String userInfoUrl = authThirdClientProperties.getUserInfoUrl();
if(ObjectUtil.isEmpty(userInfoUrl)) {
throw new CommonException("未配置第三方用户信息接口地址无法使用Token交换登录");
}
// 用accessToken调用第三方用户信息接口
JSONObject thirdUserInfo = requestThirdUserInfo(userInfoUrl, authThirdTokenLoginParam.getAccessToken());
// 从第三方返回中解析用户信息固定字段account、email、phone
String thirdAccount = thirdUserInfo.getStr("account");
String thirdEmail = thirdUserInfo.getStr("email");
String thirdPhone = thirdUserInfo.getStr("phone");
// 三级降级匹配本地用户account → email → phone
SaBaseLoginUser saBaseLoginUser = matchLocalUser(thirdAccount, thirdEmail, thirdPhone);
// 执行B端登录
return execLoginB(saBaseLoginUser, AuthDeviceTypeEnum.PC.getValue());
}
/**
* 调用第三方用户信息接口
* 规范GET请求Header传Authorization: Bearer xxx
* 返回格式:{code:200, data:{account,email,phone}}
*/
private JSONObject requestThirdUserInfo(String userInfoUrl, String accessToken) {
try {
HttpResponse response = HttpRequest.get(userInfoUrl)
.header("Authorization", "Bearer " + accessToken)
.timeout(10000)
.execute();
if(!response.isOk()) {
throw new CommonException("调用第三方用户信息接口失败HTTP状态码{}", response.getStatus());
}
String body = response.body();
if(ObjectUtil.isEmpty(body)) {
throw new CommonException("第三方用户信息接口返回为空");
}
JSONObject resultJson = JSONUtil.parseObj(body);
// 校验code
Integer code = resultJson.getInt("code");
if(code == null || code != 200) {
throw new CommonException("第三方用户信息接口返回业务异常code={}", code);
}
// 提取data
JSONObject dataJson = resultJson.getJSONObject("data");
if(ObjectUtil.isEmpty(dataJson)) {
throw new CommonException("第三方用户信息接口返回数据中未找到data字段");
}
return dataJson;
} catch (CommonException e) {
throw e;
} catch (Exception e) {
throw new CommonException("调用第三方用户信息接口异常:{}", e.getMessage());
}
}
/**
* 三级降级匹配本地用户account → email → phone
*/
private SaBaseLoginUser matchLocalUser(String thirdAccount, String thirdEmail, String thirdPhone) {
// 优先通过account查找
if(StrUtil.isNotBlank(thirdAccount)) {
SaBaseLoginUser user = loginUserApi.getUserByAccount(thirdAccount);
if(ObjectUtil.isNotEmpty(user)) {
return user;
}
}
// 其次通过email查找
if(StrUtil.isNotBlank(thirdEmail)) {
SaBaseLoginUser user = loginUserApi.getUserByEmail(thirdEmail);
if(ObjectUtil.isNotEmpty(user)) {
return user;
}
}
// 最后通过phone查找
if(StrUtil.isNotBlank(thirdPhone)) {
SaBaseLoginUser user = loginUserApi.getUserByPhone(thirdPhone);
if(ObjectUtil.isNotEmpty(user)) {
return user;
}
}
// 三种方式都找不到
throw new CommonException("第三方用户account={}, email={}, phone={})在本系统中未找到对应用户,请联系管理员同步用户",
thirdAccount, thirdEmail, thirdPhone);
}
/**
* 校验是否开启注册
*