认证提供商
通过创建认证提供商,可以使用基于OAuth2.0协议的身份验证提供商的用户身份登录纷享销客,从而实现单点登录 (SSO)。 可通过配置实现单点登录(基于OpenID Connect协议),或者创建一个“认证提供商”命名空间的自定义APL代码实现单点登录。
OpenID Connect
如果认证提供商的身份认证协议是基于OpenID Connect的,可以通过配置实现(OpenID Connect是基于OAuth 2.0规范的可互操作的身份验证协议)。

用户需要配置以下信息:
- 提供商类型:OpenId Connet 
- 名称:认证提供商名称(最长50个字符) 
- URL后缀:用于生成Oauth2 SSO登入、登出和回调地址(最长50个字符,仅可包含下划线和字母数字字符。必须以字母开头,不包括空格,不得以下划线结尾,且不得包含两个连续下划线) 
- Consumer Key:身份验证提供方发行的Consumer Key(Client ID) 
- Consumer Secret:身份验证提供方发行的Consumer Secret(Client Secret) 
- Token接口地址:获取身份验证提供方Token的API地址 
- 用户信息接口地址:获取身份验证提供方用户信息的API地址 
- 授权范围:身份验证提供方提供,允许访问用户信息的权限描述(不填写时默认传openid) 
- 外部用户身份认证字段:从身份验证提供方的用户信息中,选择一个字段作为用户身份验证字段,需要确保此字段值的唯一性 
- 纷享用户身份认证字段:从纷享的人员对象中,选择一个字段的API Name作为用户身份验证字段,需要确保此字段值的唯一性 - 单点登录身份验证时,会使用身份验证提供方返回的用户信息的“外部用户身份认证字段”字段值,去查询纷享人员的“纷享用户身份认证字段”字段值,如果有返回结果则身份验证成功。 
- 自定义登出地址:需要单点登出时访问的身份验证提供方的登出地址 
保存配置后,系统会在下方显示生成的单点登录地址,回调地址和登出地址:
1.单点登录地址:单点登录的入口地址
2.回调地址:配置在身份验证提供方的回调地址
3.登出地址:配置在身份验证提供方的单点登出地址
自定义认证提供商
通过创建“认证提供商”命名空间的自定义APL代码(目前该命名空间只支持Groovy的APL类)实现非标准化的Oauth2.0单点登录。
代码例子:
/**
 * @type classes
 * @returntype
 * @namespace custom_oauth_protocol
 */
class AAA implements AuthProviderPlugin {
    /**
     * 跳转idp登录url
     * redirect_uri为认证提供商生成的回调地址
     * client_id身份认证提供商发行的client id
     * 其他参数根据身份认证提供商的要求依次在该方法的参数中设置即可
     */
    @Override
    String authorizeUrl() {
        String authorizeUrl = "xxx/oauth2/auth"
        final Map<String, String> param = [
                "response_type": "code",
                "response_mode": "query",
                "scope"        : "openid",
                "redirect_uri" : "xxx.my.fxiaoke.com/oauth2/sp/callback/xxx",
                "client_id"    : "xxxx"
        ]
        return appendUrl(authorizeUrl, param)
    }
    /**
     * idp回调
     * @param params idp登录成功后回调时传的参数(通常包含code)
     * @return 返回纷享内员工id
     * 该方法通常需要实现的逻辑:
     * 1.从入参中获取code,用code去身份认证提供商获取token
     * 2.获取token后,使用token去获取身份认证提供商的用户信息(外部用户信息)
     * 3.用外部用户信息去查询对应的纷享用户信息
     * 4.返回查询到的有效的纷享人员id,则代表认证成功。返回无效的纷享人员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;
    }
    /**
     * 登出纷享时,同时跳转到该地址,登出idp
     * @return 返回null时跳转到纷享登录页
     */
    @Override
    String logoffUrl() {
        return "xxx/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", "xxx")
                .field("scope", "openid")
                .field("redirect_uri", "https://xxx.my.fxiaoke.com/oauth2/sp/callback/xxx")
                .field("client_secret", "xxxx")
                .field("code", parameterMap["code"].get(0))
                .build()
        Request request = Request.builder()
                .method("POST")
                .url("https://xxxx/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://xxx/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) {
    }
}
完成代码编写后在认证提供商页面绑定代码后即可完成配置。
