认证提供商
配置认证提供商以接入外部 OAuth 2.0 / OpenID Connect 身份源或使用 APL 编写自定义单点登录插件。
概述
💡 通过创建认证提供商,管理员可以将平台与基于 OAuth 2.0 或 OpenID Connect 协议的第三方身份提供商(IdP)打通。这允许员工使用其已有的外部账号一键登录平台,实现身份统一管理。针对非标准化的第三方协议,平台支持通过编写 APL 插件代码来实现对接。
开始之前
IMPORTANT
- 角色权限:您必须拥有超级管理员(System Administrator)权限。
- 信息准备:配置前需先在第三方身份认证平台(如 Okta、企业微信)创建应用,并获取其发行的
Client ID(Consumer Key)和Client Secret(Consumer Secret)。
方式一:配置标准 OpenID Connect 认证提供商
前往 系统管理 > 企业设置 > 安全认证 > 认证提供商。
点击 新建,在“提供商类型”中选择 OpenID Connect。

配置以下基础与接口参数:
- 名称:输入认证提供商的名称(最长 50 个字符)。
- URL后缀:设置唯一的后缀(最长 50 个字符,仅能包含字母、数字和下划线,必须以字母开头,不包含空格,不能以下划线结尾且不能有连续的两个下划线)。该后缀将用于在系统底部生成唯一的单点登录入口地址。
- Consumer Key:填写第三方身份验证提供方发行的 Client ID。
- Consumer Secret:填写第三方身份验证提供方发行的 Client Secret。
- Token接口地址:获取第三方令牌的 API 请求地址。
- 用户信息接口地址:第三方返回用户基础属性的 API 请求地址。
- 授权范围:允许获取的用户信息权限范围描述(不填则默认为
openid)。
配置用户映射验证关系:
- 外部用户身份认证字段:从第三方返回的用户 JSON 属性中,选择一个唯一属性(如
email或employee_number)作为外部标识。 - 纷享用户身份认证字段:从平台系统的人员对象中,选择一个字段的 API 属性名(API Name,如
employee_number)作为匹配键。
NOTE
当员工使用单点登录时,平台自动调用接口获取外部用户的认证值,并在本地人员表中检索是否存在“纷享用户身份认证字段”与其完全相等的员工账号。如果匹配成功,则代表验证通过并成功登录。
- 自定义登出地址:指定用户登出时需要跳转的第三方注销 API 地址。
- 外部用户身份认证字段:从第三方返回的用户 JSON 属性中,选择一个唯一属性(如
点击 保存。系统会在页面下方自动生成三条只读地址,请复制并填回您的第三方 IdP 控制台中:
- 单点登录地址:员工登录系统的专用入口。
- 回调地址:第三方登录成功后,安全重定向回平台的地址。
- 登出地址:退出登录时的注销地址。
方式二:使用 APL 编写自定义认证提供商插件
针对非标准协议或包含特殊加密要求的身份认证,管理员可以使用 APL(Groovy 语言)自定义逻辑:
- 前往 系统管理 > 企业设置 > 安全认证 > 认证提供商。
- 点击 新建,类型选择 自定义插件。
- 在代码编辑器中编写实现类。该类必须继承
AuthProviderPlugin接口并实现其核心方法。 - APL 插件代码结构示例如下:
groovy
/**
* @type classes
* @returntype
* @namespace custom_oauth_protocol
*/
class AAA implements AuthProviderPlugin {
/**
* 重定向跳转至第三方 IdP 的登录地址
* redirect_uri 为系统生成的回调地址
* client_id 为第三方发行的 client id
*/
@Override
String authorizeUrl() {
String authorizeUrl = "https://example.com/oauth2/auth"
final Map<String, String> param = [
"response_type": "code",
"response_mode": "query",
"scope" : "openid",
"redirect_uri" : "https://example.my.fxiaoke.com/oauth2/sp/callback/AAA",
"client_id" : "client-id-placeholder"
]
return appendUrl(authorizeUrl, param)
}
/**
* 第三方回调处理
* @param params 登录成功回调时携带的参数(如授权码 code)
* @return 返回匹配成功的纷享内部人员主键 ID
*/
@Override
Integer callback(Map<String, List<String>> params) {
String token = getAccessToken(params)
log.info(token)
Integer empId = getEmployeeId(token)
log.info(empId)
return empId;
}
/**
* 单点登出重定向地址
* @return 返回 null 时,系统默认跳转回登录页
*/
@Override
String logoffUrl() {
return "https://example.com/oauth2/logoff"
}
private static String appendUrl(String url, Map<String, String> data) {
String paramStr = ''
data.each { String key, value -> paramStr += key + "=" + value + "&" }
paramStr = paramStr.substring(0, paramStr.length() - 1)
return url.contains("?") ? (url + "&" + paramStr) : (url + "?" + paramStr);
}
private String getAccessToken(Map<String, List<String>> parameterMap) {
FormBody body = FormBody.builder()
.field("client_id", "client-id-placeholder")
.field("scope", "openid")
.field("redirect_uri", "https://example.my.fxiaoke.com/oauth2/sp/callback/AAA")
.field("client_secret", "client-secret-placeholder")
.field("code", parameterMap["code"].get(0))
.build()
Request request = Request.builder()
.method("POST")
.url("https://example.com/oauth2/token")
.timeout(7000)
.retryCount(0)
.body(body)
.build()
HttpResult o = (HttpResult) http.execute(request).getData()
log.info(o)
return o.content["access_token"].toString()
}
Integer getEmployeeId(String accessToken) {
HttpResult o = (HttpResult) http.get("https://example.com/oauth2/userinfo", ["Authorization": accessToken]).getData();
String user = o.content["sub"].toString();
log.info(user)
QueryResult data = (QueryResult) object.find("PersonnelObj",
[["employee_number": user]],
2,
0
).getData()
List<Map> dataList = data.dataList
if (dataList.size() <= 0) {
message.throwErrorMessage("找不到对应的用户, user:" + user)
}
if (dataList.size() > 1) {
message.throwErrorMessage("找到多个对应的用户, user:" + user)
}
return dataList[0]["user_id"].toString() as Integer
}
// debug 测试入口
static void main(String[] args) {
}
}保存并编译代码,发布后将其与自定义认证提供商绑定生效。


