# sso - 单点登录扩展

单点登录扩展(Single Sign-On)帮助第三方开发者实现一个个性化的、可复用的、可配置化的单点登录扩展插件。安装单点登录扩展插件后,会在系统设置 > 单点登录中自动显示一个新的单点登录配置方案,用户选择启用后可显示扩展提供的配置参数,配置完成并保存后即可使用安装的单点登录扩展进行系统登录认证。

通过扩展插件可以实现多种常见的单点登录需求:

  1. 增加一个个性化的OAuth2单点登录机制,如支持企业微信
  2. 支持LDAP服务器、Windows域认证
  3. 支持某些特定HTTP头的身份认证
  4. 数字证书单点登录
  5. ……

# 扩展文件结构

/
├──package.json //定义扩展的配置信息
├──main.action.ts //扩展的主体代码,提供后端的脚本逻辑
├──main.action //main.action.ts编译后产生的文件
└──thumbnail.png //缩略图

# package.json

package.json中可以配置扩展提供出去的配置选项,这些选项会出现在系统设置 > 单点登录中。

"contributes":{
    "sso":{
        "settingsForm":{},
    }
}

# main.action.ts

单点登录主要是后端的逻辑,main.action.ts是扩展的后端脚本的默认入口,定义了单点登录扩展需要实现的一些函数逻辑。

/**
 * 通过脚本定义一个个性化的单点登录方案。
 * 
 * 1. 单点登录扩展需要安装到系统,并在【系统设置】-【单点登录】中启用才会真正的生效。
 * 2. 此脚本文件提供了一些约定的函数,扩展实现者按需实现这些函数即可实现自己想要的单点登录机制,并且可以被复用到不同的系统环境下。
 * 3. 可以实现类似OAuth2、CAS的单点登录流程、根据特定的http头进行自动登录、或基于LDAP进行身份验证的登录流程
 * 4. 可以使用`svr-api/sys`中的`getSysSetting`函数获取相关系统设置信息。
 */

/**
 * 当一个未登录的用户访问了需要进行登录才能访问的资源时调用。
 * 
 * 1. 此函数可以帮助实现根据特定的url参数、http头、甚至是IP地址特征等信息进行自动登录。
 * 2. 此函数不是必须实现的,`onNeedSignin`、`onSSOGetAuthRedirectURL`、`onPasswordSignin`通常应该至少实现一个。
 * 
 * @param request web请求对象
 * @param response web响应对象
 * @param ssoArgs 当前正在使用单点登录方案的配置参数信息
 * @returns
 * 	1. 明确返回一个`UserInfo`对象,那么系统将无需用户输入相关密码直接使用返回的用户信息登录并初始化权限信息。  
 * 	2. 返回`true`,表示此函数已经自己处理了response响应信息(比如`response.sendError(403)`,系统将不再继续处理此次Web请求。
 * 	3. 如果抛出异常,那么将提示异常信息给用户。  
 * 	4. 没有返回值,那么系统将继续走后续的登录流程机制,相当于未实现此函数。此时调用者也可以通过自己调用脚本api`loginWithoutPassword`实现登录。
 */
// function onNeedSignin(request: HttpServletRequest, response: HttpServletResponse, ssoArgs: SSOArgs): UserInfo | boolean | void {
// 	return null;
// }

/**
 * 当用户在系统默认的用户名密码登录框中输入用户密码信息进行登录时调用。
 *
 * 1. 可用于支持向第三方LDAP、Windows域、或数据库表进行用户身份信息判断。
 * 2. 账号为 admin 的用户登录时不受这个脚本函数的影响
 * 3. 此函数不是必须实现的,`onNeedSignin`、`onSSOGetAuthRedirectURL`、`onPasswordSignin`通常应该至少实现一个。
 * 
 * @param request
 * @param response 
 * @param userId 用户输入的用户ID或者手机号
 * @param password 明文密码
 * @param ssoArgs 当前正在使用单点登录方案的配置参数信息
 * @returns
 * 	1. 返回`UserInfo`对象,那么系统将直接使用返回的用户信息登录初始化权限信息。   
 * 	2. 如果抛出异常,那么将提示异常信息给用户。 
 * 	3. 如果没有返回值,那么将进行系统默认的登录验证。
 */
// function onPasswordSignin(request: HttpServletRequest, response: HttpServletResponse, userId: string, password: string, ssoArgs: SSOArgs): UserInfo | void {
// }

/**
 * 当一个用户登录成功后,执行一次此函数。
 * 
 * 1. 可以通过此钩子函数修改当前用户的登录信息(包括权限列表等)。
 * 2. 此函数不是必须实现的
 *
 * @param request
 * @param response
 * @param user 当前用户
 * @param ssoArgs 当前正在使用单点登录方案的配置参数信息
 * @throws 抛出异常,那么将导致用户登录失败,可用于进行额外的登录验证判断。
 */
// function onDidSignin(request: HttpServletRequest, response: HttpServletResponse, user: CurrentUser, ssoArgs: SSOArgs): void {
// }

/**
 * 判断访问服务器所使用的app是不是当前单点登录方案所能支持的
 * 
 * 调用场景说明
 * 1. 在移动端登录界面,可以尝试使用当前app所登录用户授权登录到系统中比如:
 *    - 在微信浏览器中点击微信登录按钮,获取`微信用户授权`的时候,判断当前访问服务器的app是不是`微信`,如果是微信则尝试跳转到微信的授权界面
 *    - 在企业微信、钉钉等应用中访问本系统同微信
 * 2. 在 PC二维码登录界面,在使用移动端app扫码登录的时候,判断该app是否是可用来扫码登录,如果可以,则尝试跳转到 app 对应的授权页面获取app登录用户授权
 * @param userAgent 服务器收到请求的请求头 User-Agent
 * @param request
 * @param ssoArgs 当前正在使用单点登录方案的配置参数信息
 */
// function isAppSupported(userAgent: string, request: HttpServletRequest, ssoArgs: SSOArgs): boolean {
// }

/**
 * 构造一个URL,用于显示第三方认证授权服务器的授权页面。如:
 * 
 * 1. QQ手机扫码登录斗鱼:<https://xui.ptlogin2.qq.com/cgi-bin/xlogin?appid=716027609&pt_3rd_aid=101047385&daid=383&pt_skey_valid=0&style=35&s_url=http%3A%2F%2Fconnect.qq.com&refer_cgi=authorize&which=&client_id=101047385&redirect_uri=https%3A%2F%2Fpassport.douyu.com%2Findex%2Funion%2Findex%2Fqq&state=6fc429913eed76f55dc59714fd6d5d46&scope=&response_type=code&approval_prompt=force&chInfo=ch_share__chsub_CopyLink>
 * 2. 迅雷PC页面请求QQ授权:<https://graph.qq.com/oauth2.0/show?which=Login&display=pc&client_id=101164677&redirect_uri=https%3A%2F%2Flogin-i-ssl.xunlei.com%2Fthirdlogin%3FOSVersion%3DMacIntel%26appName%3DWEB-vip.xunlei.com%26appid%3D101%26clientVersion%3DNONE%26deviceModel%3Dchrome%252F87.0.4280.88%26deviceName%3DPC-Chrome%26devicesign%3Dwdi10.8c2ba76bd4d351a44e0fb0275e1eaa89dac5aba824761b9e4e8dedf8fa27fe68%26format%3Dcookie%26netWorkType%3DNONE%26platformVersion%3D1%26protocolVersion%3D300%26providerName%3DNONE%26redirectUrl%3Dhttps%253A%252F%252Fvip.xunlei.com%252F%26sdkVersion%3Dv4.5.11%26thirdAppid%3D%26thirdType%3Dqq&response_type=code>
 * 3. 获取微信授权<https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxa79ae207bd77d088&redirect_uri=http://chenshir.succez.com/api/auth/{id}/{wechat}/redirect&response_type=code&scope=snsapi_userinfo&state=1223#wechat_redirect>
 * 
 * 调用场景:
 * 
 * 1. 系统设置了默认的单点登录方式为OAuth2或CAS等服务,当用户第一次访问SuccBI系统页面需要登录时系统会调用此函数构造一个授权页
 * 	面的URL,用于显示第三方授权页面,并在授权成功后重定向到参数`redirect_uri`指定的页面。
 * 2. 在PC扫码登录的场景中(如用微信或支付宝扫码),手机从电脑电脑屏幕扫码后手机上会显示微信标准的授权页面,提示用户授权(如果用户
 * 	之前已经授权过了,那么不会提示,微信会静默授权),授权成功后会定位到参数`redirect_uri`指定的页面。
 * 
 * 注意事项:
 * 
 * 1. 实现者可以根据`request`参数判断用户的设备和app
 * 2. 当使用pc扫码登录的场景时,如果返回null 则表明当前所使用浏览器不是单点登录方式所指定的app
 * 3. 系统中配置了多个单点登录方案(ssoid)都支持扫码时,如果用户用微信扫码,那么应该调用哪个实现的函数呢(支付宝也有类似问题)?
 * 	系统会在用户用app扫码后(扫的是`/api/auth/qrcode?app=xxxx&id={qrcodeid}`这样的地址),以此调用所有单点登录方案的此函数,
 * 	直到一个实现返回了期望的值,如`true`、一个URL地址。
 * 
 * @see <https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html>
 * @param request web请求对象
 * @param response web响应对象
 * @param redirect_uri 实现者应该使用此参数沟通返回的url中的`redirect_uri`参数,这样当收到验证票据或者根据code换取令牌的请求时系统才会自动定位到`onSSOCheckTicket`函数。
 * @param ssoArgs 当前正在使用单点登录方案的配置参数信息
 * @returns 
 * 	- 返回`false`,表示当前用户使用的app不能扫码登录。
 * 	- 返回`true`,则表明当前单点登录方案支持这个app登录,且脚本函数自己返回的授权页面,系统不需要显示默认的了。
 * 	- 返回一个URL地址,应该是一个可以用于重定向的完整的URL地址。
 */
// function onSSOGetAuthRedirectURL(request: HttpServletRequest, response: HttpServletResponse, redirect_uri: string, ssoArgs: SSOArgs): boolean | string {
// 	return null;
// }

/**
 * 用于用户授权后向第三方认证服务器验证授权信息的合法性并获得相关授权用户的必要信息。
 * 
 * 使用场景:
 * 
 * 1. OAuth2协议中,根据code获取令牌。或者CAS协议中验证票据合法性。此时前端是使用http重定向方式将授权信息传递给后台的。
 * 2. 小程序登录场景中,当用户授权小程序页面后,小程序会通过ajax把授权信息(加密的)发送给SuccBI后端进行解密验证
 * 
 * 注意事项:
 * 
 * 1. 当实现了`onSSOGetAuthRedirectURL`函数时,通常也需要实现此函数。
 * 2. state参数系统已经判断过了
 * 3. 此函数只需要验证票据的合法性获得用户授权的用户信息,系统会自动根据系统设置中的设置决定是否允许用户登录
 * 4. 如果验证失败,那么应该抛出异常。
 * 
 * @param request web请求对象,可通过`request.getParameter()`获取url上的参数,通过`request.getRequestBody()`函数
 * 	获取POST方式发送的参数,如果请求的`Contect-Type`是`application/json` 那么`request.getRequestBody()`返回json对象。
 * @param ssoArgs 当前正在使用单点登录方案的配置参数信息
 * @returns 返回 验证票据所返回的用户信息
 * 	1. 如果返回的用户信息中有明确的用户目录信息(`userDirectory`,`sys`表示内部用户,`external`表示外部用户),那么系统将自
 * 		动根据用户的目录去决定使用内部用户还是外部用户进行匹配
 * 	2. 返回的用户信息的属性如果在用户表中有同名字段(物理字段名同名,忽略大小写),那么在新增用户时将自动写入这些字段
 * 	3. 为了以后支持多个公众号,系统会默认先找`ssoid+下划线+属性名`的字段是否存在,如果存在,优先用这个。
 * @throws 如果验证失败,那么应该抛出异常。
 */
// function onSSOCheckTicket(request: HttpServletRequest, response: HttpServletResponse, ssoArgs: SSOArgs): UserInfo {
// 	return null;
// }

/**
 * 用于注销当前登录的时候,同时注销提供单点登录服务的主系统
 * 
 * 1. 提供redirect_uri 的参数供根据业务情况,处理是否需要在注销主系统后,再跳回到本系统地址
 * 2. 此函数不是必须实现的
 * 
 * @param request
 * @param response
 * @param redirect_uri
 * @param ssoArgs 当前正在使用单点登录方案的配置参数信息
 */
// function onSSOGetLogoutRedirectURL(request: HttpServletRequest, response:HttpServletResponse, redirect_uri: string, ssoArgs: SSOArgs): string {
// 	return null;
// }

/**
 * 从第三方平台(如企业微信、钉钉)获取企业所有成员的基本信息。
 * 
 * 1. 使用场景:系统绑定企业平台账号后,通过比较系统用户库与企业平台账号中的所有成员信息,更新系统用户库
 * 2. 此函数不是必须实现的,如果实现了,表明当前单点登录方案支持批量获取用户信息
 *
 * @param ssoArgs 当前正在使用单点登录方案的配置参数信息
 * @return 一次性返回所有用户的信息
 * 	1. 返回的用户信息的属性如果在用户表中有同名字段(物理字段名同名,忽略大小写),那么在新增用户时将自动写入这些字段
 * 	2. 为了以后支持多个公众号,系统会默认先找`ssoid+下划线+属性名`的字段是否存在,如果存在,优先用这个。
 */
// function fetchUsers(ssoArgs: SSOArgs): Array<UserInfo> {
// 	return null;
// }

/**
 * 从第三方平台(如企业微信、钉钉)获取系统所绑定企业所有部门的基本信息。
 *
 * 1. 使用场景:系统绑定企业平台账号后,同步系统部门信息与第三方企业平台上的企业部门信息数据
 * 2. 此函数不是必须实现的,如果实现了,表明当前单点登录方案支持批量获取部门信息
 *
 * @param ssoArgs 当前正在使用单点登录方案的配置参数信息
 * @return 一次性返回所有部门的信息
 * 	1. 返回的部门信息的属性如果在部门表中有同名字段(物理字段名同名,忽略大小写),那么在新增部门时将自动写入这些字段
 * 	2. 为了以后支持多个公众号,系统会默认先找`ssoid+下划线+属性名`的字段是否存在,如果存在,优先用这个。
 */
// function fetchtDepartments(ssoArgs: SSOArgs): Array<DeptInfo> {
// 	return null;
// }

/**
 * 从第三方平台(如钉钉)获得所有的用户组信息。
 * 
 * 1. 使用场景:系统绑定企业平台账号后,同步系统用户组信息与第三方企业平台上的用户组数据
 * 2. 此函数不是必须实现的,如果实现了,表明当前单点登录方案支持批量获取用户组信息
 *
 * @param ssoArgs 当前正在使用单点登录方案的配置参数信息
 * @return 一次性返回所有用户组的信息
 * 	1. 返回的用户组信息的属性如果在用户组表中有同名字段(物理字段名同名,忽略大小写),那么在新增部门时将自动写入这些字段
 * 	2. 为了以后支持多个公众号,系统会默认先找`ssoid+下划线+属性名`的字段是否存在,如果存在,优先用这个。
 */
// function fetchtUserGroups(ssoArgs: SSOArgs): Array<UserGroupInfo> {
// 	return null;
// }

/**
 * 用于在配置界面上测试用户的配置是否正确。
 * 
 * @param conf 用户在配置界面上所做的配置(可能还未保存)
 * @returns 如果测试成功,明确返回true,测试失败抛出异常
 */
// function testConf(conf: JSONObject): boolean {
// 	return null;
// }
是否有帮助?
0条评论
评论