EOS Low-Code Platform 8 EOS Low-Code Platform 8
产品简介
学习指南
更新说明
安装与集成
上线指南
初见EOS
低代码开发手册
专业代码开发手册
智能体开发手册
专题场景实战
公共服务框架
应用运行治理
运维指南
  • 场景 1:第三方 userinfo 接口要求 GET 方式(而非默认的 POST)
  • 场景 2:token 接口返回嵌套 JSON 结构
  • 场景 3:token 接口要求 POST Form(而非 JSON)

# 场景 1:第三方 userinfo 接口要求 GET 方式(而非默认的 POST)

背景:根据 OIDC 标准,userinfo 端点 MUST 支持 GET。AFCenter 默认实现用的是 POST,如果你的 OAuth2 服务端严格按标准只接受 GET,需要做此扩展。

完整改造步骤:

第 1 步:创建自定义实现类

在模块 com.primeton.gocom.afcenter.oauth 中创建包 com.primeton.gocom.afcenter.oauth.third,新建类:

package com.primeton.gocom.afcenter.oauth.third;

import com.eos.access.http.OnlineUserManager;
import com.eos.data.datacontext.IUserObject;
import com.eos.data.datacontext.UserObject;
import com.primeton.gocom.afcenter.api.oauth.ILoginService;
import com.primeton.gocom.afcenter.api.oauth.IOauthCustomService;
import com.primeton.gocom.afcenter.api.user.IUserService;
import com.primeton.gocom.afcenter.common.exception.AFCExceptionCode;
import com.primeton.gocom.afcenter.common.oauth2.Oauth2RestTemplateUtil;
import com.primeton.gocom.afcenter.common.service.AfcServiceProxyFactory;
import com.primeton.gocom.afcenter.model.org.User;
import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 自定义 OAuth2 客户端 —— 适配 userinfo 接口使用 GET 方式的场景
 *
 * 与默认实现的唯一区别:ueserInfo() 使用 GET 而非 POST
 */
public class GetUserInfoOauthCustomService implements IOauthCustomService {

    private static ILoginService loginService = AfcServiceProxyFactory
            .getService(ILoginService.class, true);
    private static IUserService userService = AfcServiceProxyFactory
            .getService(IUserService.class, true);

    private String userKey;

    public GetUserInfoOauthCustomService(String userKey) {
        this.userKey = userKey;
    }

    /**
     * ★ 核心改动:用 GET 方式请求用户信息
     */
    @Override
    public String ueserInfo(String accessToken, String userinfoUrl) throws Exception {
        Map<String, String> header = new HashMap<>();

        // ★ 注意:此处按 OIDC 标准添加了 "Bearer " 前缀
        // 如果第三方不要求前缀,改为 header.put("Authorization", accessToken);
        header.put("Authorization", "Bearer " + accessToken);

        // ★ 使用 GET 方法替换默认的 postJson
        String result = Oauth2RestTemplateUtil.get(userinfoUrl, header, null);

        if (StringUtils.isNotBlank(result)) {
            // 如果配置了 user-key,从 JSON 中提取对应字段
            if (StringUtils.isNotBlank(userKey)) {
                // TODO: 根据实际 JSON 解析库提取 userKey 对应的值
                // 示例(Jackson):
                // JsonNode node = new ObjectMapper().readTree(result);
                // result = node.findValue(userKey).textValue();
            }
            if (StringUtils.isBlank(result)) {
                throw AFCExceptionCode.OAUTH_SSO_USERINFO_EMPTY.runtimeException();
            }
        }
        return result;
    }

    /**
     * token 获取 —— 与默认实现一致,如需修改照此模式
     */
    @Override
    public String accessToken(String code, String tokenUrl, String clientId,
                              String clientSecret, String redirectUrl) throws Exception {
        // 如果 token 接口与默认实现一致,可以直接复制
        // DefaultOauth2ClientServiceImpl.accessToken() 的逻辑
        StringBuilder sb = new StringBuilder(tokenUrl);
        sb.append("?grant_type=authorization_code")
          .append("&client_id=").append(clientId)
          .append("&client_secret=").append(clientSecret)
          .append("&code=").append(code)
          .append("&redirect_uri=").append(redirectUrl);
        // 注意:此处也用 POST 请求 token 接口
        return Oauth2RestTemplateUtil.postJson(sb.toString(), null, null);
    }

    /**
     * 用户对象初始化 —— 与默认实现一致
     */
    @Override
    public IUserObject initUserObject(String userValue) throws Exception {
        User user = userService.findUserByCode(userValue, null);
        String userId;
        if (user != null) {
            userId = user.getId();
        } else {
            userId = userValue;
        }
        UserObject userObject = loginService.createUserObjectWithUserId(userId, null);
        OnlineUserManager.login(userObject, loginService.getSessionTime(), TimeUnit.SECONDS);
        return userObject;
    }
}

第 2 步:注册自定义实现

在 AFCServerAutoConfiguration 类中(或任意 @Configuration 类)添加 Bean 声明:

// ★ Bean 名称规则:oauth2Client-{连接器编码}
// 假设你的连接器编码是 "corp-sso"
@Bean("oauth2Client-corp-sso")
public IOauthCustomService corpSsoCustomService() {
    return new GetUserInfoOauthCustomService("userId"); // userKey 从连接器配置传入
}

第 3 步:在连接器配置界面中配置

确保连接器的「编码」字段与 Bean 名称后缀一致(即 corp-sso)。系统在 ThirdServiceImpl 中会根据连接器编码查找对应的自定义 Bean。

第 4 步:重启并验证

重启 AFCenter 后,用 curl 模拟验证:

# 获取一个有效的 access_token(从正常登录流程中获取)
# 然后手动测试自定义实现是否生效
curl -X GET "http://sso.corp.com/oauth2/user-info" \
  -H "Authorization: Bearer <access_token>"

也可以在代码中加日志确认走了自定义逻辑。


# 场景 2:token 接口返回嵌套 JSON 结构

背景:第三方 token 接口返回的 access_token 不在顶层字段,而是嵌套结构,例如:

{
  "code": 0,
  "data": {
    "access_token": "eyJ...",
    "expires_in": 3600
  }
}

此时 access-token-key 配置为 access_token 是不够的,因为默认实现只能提取顶层字段。

解决方案:实现 IOauthCustomService.accessToken(),自定义 JSON 解析:

@Override
public String accessToken(String code, String tokenUrl, String clientId,
                          String clientSecret, String redirectUrl) throws Exception {
    // 构造请求 URL(如果第三方要求 POST Form,改用 postForm)
    StringBuilder sb = new StringBuilder(tokenUrl);
    sb.append("?grant_type=authorization_code")
      .append("&client_id=").append(clientId)
      .append("&client_secret=").append(clientSecret)
      .append("&code=").append(code)
      .append("&redirect_uri=").append(redirectUrl);

    String response = Oauth2RestTemplateUtil.postJson(sb.toString(), null, null);

    // ★ 自定义解析:处理嵌套 JSON
    JsonNode root = new ObjectMapper().readTree(response);
    // 提取 data.access_token
    String accessToken = root.path("data").path("access_token").asText();
    if (StringUtils.isBlank(accessToken)) {
        throw AFCExceptionCode.OAUTH_SSO_ACCESSTOKEN_EMPTY.runtimeException();
    }
    return accessToken;
}

# 场景 3:token 接口要求 POST Form(而非 JSON)

@Override
public String accessToken(String code, String tokenUrl, String clientId,
                          String clientSecret, String redirectUrl) throws Exception {
    // ★ 使用 POST Form 替代默认的 POST JSON
    MultiValueMap<String, Object> formData = new LinkedMultiValueMap<>();
    formData.add("grant_type", "authorization_code");
    formData.add("client_id", clientId);
    formData.add("client_secret", clientSecret);
    formData.add("code", code);
    formData.add("redirect_uri", redirectUrl);

    String response = Oauth2RestTemplateUtil.postForm(tokenUrl, null, formData);

    // 后续解析同上...
    return response;
}