一、功能背景

在实际项目中,SuperMap iServer 通常需与已有的统一用户认证平台进行集成,以实现 SSO 单点登录功能。对于常见的标准 SSO 单点登录系统,iServer 提供了 CAS、Keycloak、第三方(如 QQ 登录或微博登录)等登录模式的配置入口。然而,对于非标准的用户自定义登录系统,只能通过扩展开发来实现,其实现通常较为复杂。为支持快速、高效地与项目中已有的用户自定义登录系统进行对接,iServer 提供了一种基于配置 groovy 代码的方式支持非标准 SSO 登录认证功能。

二、配置步骤

集成过程的核心是通过调用用户自定义登录系统的用户信息 API 以获取用户身份信息,并基于该身份进行 SSO 单点登录。由于不同自定义登录系统中用户信息接口存在较大差异,iServer 提供了基于 groovy 代码的自定义配置机制,支持适配各类用户信息接口。为支持非标准 SSO 登录认证功能,您需要编写 groovy、properties 等自定义配置文件,并在 iserver-system.xml 中开启该功能。

2.1 自定义配置概述

您需要在目录【iServer 产品安装包】/webapps/iserver/WEB-INF/config/ 下创建 security-integration 文件夹,并创建如下文件:

文件全称 描述
security-integration-sso.properties 配置文件。用于开启非标准 SSO 登录认证功能,以及配置用户自定义登录系统中用户信息接口的访问 URL、调用参数、返回内容等关键信息
sso-integration.groovy SSO 单点登录需要的 Groovy 脚本。使用函数定义 SSO 单点登录全流程的全部相关接口,包括请求方法、请求参数、请求头、响应结构等信息
httpclientbuilder.groovy 配置 httpclient 的 Groovy 脚本。支持通过函数定义代理拦截请求、忽略 https 证书验证等特殊配置。一般情况下不需要创建该文件。

2.1.1 security-integration-sso.properties 配置

properties 文件用于开启非标准 SSO 登录认证功能,并配置用户自定义登录系统中用户信息接口的访问 URL、调用参数、返回内容等关键信息,使服务器能够据此进行请求。需要设置的开关及接口信息如下,请按需在 properties 文件中填写相关代码:

  • security-integration.sso.enable=true(或false)。当设置为 true 时表示开启非标准 SSO 登录认证功能,设置为 false 时关闭
  • security-integration.sso.defaultRedirectUrl=/admin-ui/home。重定向到 iServer 服务管理界面。该配置项直接填写即可,无需修改
  • security-integration.sso.login.url-pattern=跳转到的第三方 SSO 登录的 URL 模板。如:http://ip:port/login/oauth/authorize?client_id=38a8c4c4722c2fbae9d5&response_type=code&scope=openid%20profile%20email&redirect_uri={callback}&state={state}
  • security-integration.sso.userinfoapi.url=用户信息接口的请求 URL。如http://ip:port/scp-account/rest/v1/users/current-user
  • security-integration.sso.userinfoapi.required=Groovy 脚本中 createUserInfoRequest 函数所需的请求参数。若存在多个时,由“,”分隔。
  • security-integration.sso.userinfoapi.response-contenttype=json。用户信息接口的响应格式

此外,该文件还需对 Groovy 脚本将调用的不同变量,即 security-integration.sso.login.url-pattern 中定义登录跳转模板中的 URL 请求参数及 security-integration.sso.userinfoapi.required 中的参数,预定义其值的获取方式。支持的获取方式包括登录跳转 URL 请求参数、HTTP 请求、配置项三类,不同类型获取方式可定义的相关属性如下(其中 X 为自定义变量的名称,可按实际情况设置):

  • 当变量值的获取方式为登录跳转 URL 请求参数时,支持定义其请求参数名,可在 properties 文件中填写如下两行代码:
    • security-integration.sso.define.X.from=request
    • security-integration.sso.define.X.request.querykey=指定请求参数名,如:code。
  • 当内置函数 createUserInfoRequest、parseUserInfo 不足以支撑完整的第三方 SSO 登录认证功能时,可自定义请求接口。当变量值的获取方式为新增的 HTTP 请求时,支持定义接口的 URL、必选请求参数、Groovy 脚本中接口对应的函数名等相关信息,可在 properties 文件中按需填写如下代码:
    • security-integration.sso.define.X.from=http
    • security-integration.sso.define.X.http.url=接口的请求 URL。如:http://172.16.120.205:8000/api/login/oauth/access_token
    • (可选)security-integration.sso.define.X.http.function=Groovy 脚本中自定义函数的函数名。默认名称为 create{X}Request
    • (可选)security-integration.sso.define.X.http.required=对该接口请求时所需的必选参数,如:code。若存在多个时,由“,”分隔
    • (可选)security-integration.sso.define.X.http.responsejsonpath=对该接口请求时能够获取变量值的 json 字段,如:access_token
    • (可选)security-integration.sso.define.X.http.responsefunction=对该接口请求时能够获取变量值的 Groovy 脚本函数名
  • 当变量值的获取方式为配置项时,支持定义其具体的参数值,可在 properties 文件中填写如下两行代码:
    • security-integration.sso.define.X.from=config
    • security-integration.sso.define.X.config.value=具体参数值。
提示:

本文件中代码填写顺序不影响实际执行顺序。

2.1.2 sso-integration.groovy 配置

Groovy 脚本用于定义支撑完整的第三方 SSO 登录认证功能所需的函数,这些函数描述了所有相关接口的请求和响应方法,包括必选函数 createUserInfoRequest、parseUserInfo,以及其他需要的函数(由 security-integration-sso.properties 配置文件中 security-integration.sso.define.X.http.function 等属性定义)。示例如下:

def createUserInfoRequest(required) {
    return [method:"POST", query:["access-token":required.token, code:required.code], headers:[Accept:"application/json"]]
}
def parseUserInfo(Response) {
    def result = [sub:Response.id]
    return result;
}

其中,

  • createUserInfoRequest 用于构建向用户信息接口发送的请求,需要定义 HTTP 请求方法、请求参数、请求头等信息:
    • 函数的变量名:当代码中需要引用变量时,可写为 {函数变量名}.{变量}
    • method 字段:描述向该接口请求时使用的 HTTP 方法
    • query 字段:描述向该接口请求时使用的请求参数,通常可引用 properties 文件中定义的变量
    • headers 字段:描述向该接口请求时使用的请求头
  • parseUserInfo 用于处理用户信息接口返回的 json 响应格式结果
    • 函数的变量名:当代码中需要引用变量时,可写为 {函数变量名}.{变量}
    • result:描述向该接口请求时返回的响应结果中需要保留的信息字段,形式为多对映射,每对由“,”分隔。如:[openId:jsonResponse.id,nickname:jsonResponse.nickname]。其他支持的内置字段如下:
      • name:用户名称,string 类型,默认为 anonym
      • password:用户密码,string 类型
      • passwordLastModified:用户最后修改日期,Date 类型
      • description:用户描述,string 类型
      • isLocked:该用户是否被锁定,boolean 类型,默认为 false
      • roles:用户关联的角色,string[] 类型
      • userGroups:用户关联的用户组,string[] 类型
      • ownRoles: 用户自已的角色,不包括所属用户组关联的角色,string[] 类型
      • expirationTime:过期时间,Long 类型

2.1.3 httpclientbuilder.groovy 配置

该文件用于定义 configure 函数,对 HttpClientBuilder 进行自定义配置,如设置代理、超时时间、添加证书等。

  • HttpClientBuilder 的默认内容如下:

def configure(builder) {
    return builder;
}

  • 若在 HttpClientBuilder 中设置代理,示例如下

import org.apache.http.HttpHost
def configure(builder) {
    return builder.setProxy(new HttpHost("127.0.0.1", 8888));
}

2.2 iserver-system.xml 配置

为使 iServer 登录界面能够应用 SSO 账号登录模式,还需在系统配置中开启该功能。修改【iServer产品安装包】/webapps/iserver/WEB-INF/iserver-system.xml中 <SSOConfig> 节点下的配置即可:

  • <enable>:用于设置非标准 SSO 登录认证功能是否启用。默认为 false,设置为 true 时为开启。

三、配置举例

以下以某用户信息 API 为例,说明 SSO 集成配置过程。

用户信息 API 接口说明:

#接口说明

本接口用于获取当前操作人的用户信息,需要使用 token 访问本接口。具体如下:

  1. 通过 URL 传递 access_token,如:http://127.0.0.1/test?access_token=xxxxxxxx
  2. token 的获取方式请参考 http://zhi.gtmap.cn/share/9f317f41-a190-4e0e-b513-9d3f58294a69

#请求说明

方式: GET

地址: http://ip:port/scp-account/rest/v1/users/current-user

Feign:  UserManagerClient#getCurrentUser

版本:1.0.5-SNAPSHOT

#响应结果

{
    "id":"b1e5ecd0c74b4f97ba0f62efc9f22a8b",
    "username":"wqy",
    "alias":"wqy",
    "admin":1,   
    "enabled":1,
    "locked":0,
    "onLeave":0,
    "registerStatus":1,
    "signId":"ff808081840e717e01849dff44580042",
    "createAt":null,
    "updateAt":null,
    "expired":"2048-08-31 00:00:00",
    "password":null,
    "mobile":"15261807626",
    "email":null,
    "tel":null,
    "birthday":null,
    "gender":"男",
    "title":null,
    "resume":null,
    "address":null,
    "sequenceNumber":null,
    "idCard":null,
    "idCardType":"identity",
    "idCardFrontalId":null,
    "idCardReverseId":null,
    "userType":0,
    "jobNumber":null,
    "partyMember":"N",
    "pictureId":null,
    "caNumber":null,
    "modifyPwd":1,
    "graduateSchool":null,
    "shortNumber":null,
    "fax":null,
    "workingYears":null,
    "entryDate":null,
    "enterpriseDTO":null,
    "roleRecordList":[
        {
            "id":"4028882a644acbd401644acc7cfa0004",
            "name":"admin",
            "alias":"管理员",
            "level":1,
            "enabled":1,
            "createAt":null,
            "updateAt":null,
            "parentRecords":[
                {
                    "id":"8aaa077165aea8ed0165aeccfc7d0002",
                    "name":"ptyg",
                    "alias":"员工",
                    "level":0,
                    "enabled":1,
                    "createAt":"2018-09-06 20:13:14",
                    "updateAt":"2022-07-21 13:53:30",
                    "parentRecords":null,
                    "excludeRecords":null,
                    "gradingRoleIds":null,
                    "gradingAuthoritys":null,
                    "orgRecords":null
                }
            ],
            "excludeRecords":null,
            "gradingRoleIds":null,
            "gradingAuthoritys":null,
            "orgRecords":null
        }
    ],
    "orgRecordList":[

    ]
}

步骤一、编写 security-integration-sso.properties

1、配置用户自定义登录系统中用户信息接口的访问 URL、调用参数、返回内容等关键信息

#启用非标准 SSO 登录认证功能
security-integration.sso.enable=true

#重定向到 iServer 服务管理界面

security-integration.sso.defaultRedirectUrl=/admin-ui/home

#根据接口说明设置跳转到的第三方 SSO 登录的 URL 模板
security-integration.sso.login.url-pattern=http://192.168.41.71/scp-account/oauth/authorize?client_id=iserver&redirect_uri={callback}&response_type=code&state={state}

#根据接口说明配置用户信息接口的请求 URL
security-integration.sso.userinfoapi.url=http://192.168.41.71/scp-account/rest/v1/users/current-user
#根据接口说明定义访问接口时需要的请求参数,即 accesstoken
security-integration.sso.userinfoapi.required=accesstoken
#设置用户信息接口的响应格式,即 json
security-integration.sso.userinfoapi.response-contenttype=json

2、根据接口说明补充其他必要变量

1)对上一步骤中所需的 accesstoken 参数的获取方式进行定义,其由 HTTP 请求获取

#定义 accesstoken 参数来源,即 HTTP 请求
security-integration.sso.define.accesstoken.from=http
#获取 accesstoken 值的接口对应的 HTTP 请求地址
security-integration.sso.define.accesstoken.http.url=http://192.168.41.71/scp-account/oauth/token
#对该接口请求时所需的必选参数
security-integration.sso.define.accesstoken.http.required=code,clientid,clientsecret
#对该接口请求时能够获取 accesstoken 参数值的响应字段
security-integration.sso.define.accesstoken.http.responsejsonpath=.access_token

2)对上一步骤中需要使用的必选请求参数(code、clientid、clientsecret)进行定义

#定义配置参数 clientid
security-integration.sso.define.clientid.from=config
security-integration.sso.define.clientid.config.value=iserver
#定义配置参数 clientsecret
security-integration.sso.define.clientsecret.from=config
security-integration.sso.define.clientsecret.value=lFEtXGu77f89p7xmdKtj
#定义请求参数 code,来源为请求参数
security-integration.sso.define.code.from=request
#进行请求时所需的请求参数名
security-integration.sso.define.code.request.querykey=code

步骤二、编写 Groovy 处理脚本

定义支撑完整的第三方 SSO 登录认证功能所需的函数,包括必选函数 createUserInfoRequest、parseUserInfo,以及用于获取 accesstoken 参数值的函数 createaccesstokenRequest

def createaccesstokenRequest(required) {
    return [method:"POST", body:[code:required.code,"grant_type":"authorization_code","redirect_uri":"http://172.16.15.206:8090/iserver/sso-callback","client_id":required.clientid,"client_secret":required.clientsecret],headers:["Content-Type":"application/x-www-form-urlencoded"]]
}
def createUserInfoRequest(required) {
    return [method:"GET", query:[access_token:required.accesstoken]]
}
def parseUserInfo(jsonResponse) {
    def result = [openId:jsonResponse.id, name:jsonResponse.username,nickName:jsonResponse.alias,roles:['ADMIN']]
    return result;
}

步骤三、在 iserver-system.xml 中开启非标准 SSO 登录认证

修改【iServer产品安装包】/webapps/iserver/WEB-INF/iserver-system.xml 中 <SSOConfig> 节点下的配置参数 <enable> 为 true。

到此,所有配置完成。重启 iServer,点击登录进入登录界面,点击底部 SSO 账号登录即可。