# 场景 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;
}