# 单点登录(SSO)

# 协同平台单点登录第三方系统

# 实现方式一:关联系统+扩展栏目

无需开发,使用关联系统管理登录信息,集成第三方系统页面。

适用于不需要验证码的Web应用,使用目标系统的用户名和密码模拟登录过程。

  • 配置关联系统

    • 以系统管理员system登录协同,使用“关联系统管理”建立一个新的关联系统。关联系统的url为被集成系统的登录链接,将登录需要的信息如用户名和密码定义为关联系统参数

      1696765362244.png

    • 建立扩展栏目

      由第三方系统管理验证信息的栏目有两种:数据集成型栏目(SSOWebContentSection)和功能操作型栏目(SSOIframeSection)

1696765371272.png

  • 用户登录后,在个人空间中配置关联系统参数,填写集成系统的登录信息(用户名和密码)。 由关联系统维护被集成系统的用户名、密码以及Session
  • 说明:被集成系统的修改:登录不成功,需要在登录请求的response中增加header项:LoginError=***,否则平台无法判断是否成功登录
  • 从V7.0开始,关联系统的URL中可以调用公式,实现一些更复杂的单点登录,请参见公式组件 (opens new window)

# 实现方式二:Ticket

实现步骤:

  • 添加页签

    新建插件sso:按下面的定义,在协同的webapps\seeyon\WEB-INF\cfgHome\plugin\sso目录下新建一个XML文件pluginCfg.xml,例如:

    <?xml version="1.0" encoding="UTF-8"?>
    <plugin>
        <id>sso</id>
        <name>单点登录模块</name>
        <category>11001</category>
    </plugin>
    

    然后在协同的webapps\seeyon\WEB-INF\cfgHome\plugin\sso\spring目录下新建一个XML文件,如myspace.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
    <beans default-autowire="byName">
        <!-- id必须唯一 -->
        <bean id="sinaNewsSpace" class="com.seeyon.v3x.common.thirdparty.ThirdpartySpace" init-method="init">
            <!--注意如果OA的版本是V6.0及其以上,上面class修正为:com.seeyon.ctp.portal.sso.thirdpartyintegration.ThirdpartySpace -->
            <!-- id必须唯一或者不写后台自动生成,必须为数字 -->
            <property name="id" value="-2327812443752403806"/>
            <!-- 页签上显示的名称 -->
            <property name="name" value="Sina News"/>
            <!-- 插件id,必须存在,如果不存在,请按下面的步骤定义一个新的插件 -->
            <property name="pluginId" value="sso"/>
            <!-- 第三方系统登录地址,如果合并3、4步,可以省略 -->
            <property name="loginURL" value="http://xxx.xxx.xxx.xxx/ssologin.jsp"/>
            <!-- 点击页签要打开的第三方系统页面地址 -->
            <property name="pageURL" value="http://xxx.xxx.xxx.xxx/main.jsp"/>
            <!-- 打开方式,值为open时在新窗口打开,为workspace时在协同页面能打开 -->
            <property name="openType" value="open"/>
            <!-- 排序号 -->
            <property name="index" value="4"/>
        <!-- 授权 accessRoles与accessCheck选择其中一个即可-->  
        <property name="accessRoles"> 
          <list> 
            <value>GeneralStaff</value> 
          </list> 
        </property>  
       <!-- <property name="accessCheck" ref="accessCheck"/> -->  
      </bean>  
     <!-- <bean id="accessCheck" class="com.seeyon.ctp.ext.Tab.TestThirdpartyAccessCheck"></bean> -->  
    </beans>
    

    注意事项:集成的第三方系统,必须先在协同平台中定义插件 (opens new window)

  • myspace.xml参数说明

名称 备注
id 唯一标示,这了在XML里必须设置为数字型
name 名称
pluginId 插件ID
loginURL 在进入第三方指定页面前,先跳转到loginURL进行握手 第三方系统根据Ticket回调获取协同身份信息,进行单点登录,并注册Ticket和登录用户的映射, 平台带着Ticket跳转到第三方系统的"页面地址" (pageURL)
pageURL 第三方指定到达的目的页面。
openType 打开方式
accessRoles与accessCheck OA用户对于第三方页签显示权限,详情请见页签集成 (opens new window)
  • 配置第三方系统,ThirdpartySpace:名称、登录地址、页面地址、打开方式【详情请见1.5.1. 页签集成】

  • 登录过程

    在协同中点击"空间页签"

    平台产生Ticket,并维护在内存中

    平台带着Ticket访问第三方系统的"登录地址"(loginURL)进行握手 第三方系统根据Ticket回调获取协同身份信息,进行单点登录,并注册Ticket和登录用户的映射, 平台带着Ticket跳转到第三方系统的"页面地址" (pageURL)

说明

第三方系统完全依赖和信任协同的身份验证,Ticket由协同平台发放

第三方系统使用与协同完全相同的登录名或者进行二次开发,自己维护协同登录名与第三方系统用户的映射表

如果当前用户已经登录第三方系统,将跳过② ③,直接到第4步,使用相同的Ticket

  • 认证ticket/获取身份信息的接口系统提供Servlet:http://a8:80/seeyon/thirdpartyController.do?ticket=**;平台将通过response (opens new window) header的LoginName返回登录名

  • 如果第三方回调后认为有异常,请在response header增加名称为SSOLogoutError的信息

  • 在上面myspace.xml中(loginURL)获取当前人员登录名例程如下:

    //获取ticket
    String ticketinfo=request.getParameter("ticket");
    //通过ticket/获取身份信息的接口获取登录信息
    String LoginNameUrl="http://127.0.0.1/seeyon/thirdpartyController.do?ticket="+ticketinfo;
    //通过HttpClientUtil发送请求
    HttpClientUtil u = new HttpClientUtil();
    //获取登录名
    String LoginName=u.getContent(LoginNameUrl).toString();
    

第三方系统退出时,要通知协同,地址是http://a8:80/seeyon/thirdparty.do?method=logoutNotify&ticket=** (opens new window)

根据情况可将 ③ ④步合并 (省略loginURL)

# 第三方系统单点登录协同平台

Ticket中请不要使用特殊字符和中文,平台不支持使用中文作为Ticket!

必须在配置工具的系统参数设置中配置协同平台的外网访问地址internet.site.url,否则,生成的url为空。

注意:【V6.1 ticket必须加密,不加密则无法正常登录】

# 应用场景

  1. 从第三方Portal打开协同
  2. 从第三方集成的协同栏目打开链接

# 关键步骤

下面描述了单点登录的典型开发关键步骤,细节可参阅后续章节

  1. 配置外网访问地址internet.site.url,如不配置,webService导出的数据的url为空

1696765426683.png

  1. 建立一个插件,作为单点登录配置和Class的容器。参见本地开发接口的插件化章节。

建立握手类,例如

public class MySSOLoginHandshake  extends SSOLoginHandshakeAbstract {
    // “ticket” 就是ticket取得的参数值
    public String handshake(String ticket) {
        if(ticket==null||ticket.equals("")) {
            return null;
        }
        HttpClient hc = new HttpClient();
        //对应下图(实现原理)中 步骤4:协同平台与第三方系统握手,根据ticket获取loginName
        loginName = hc.get("http://第三方系统/getLoginIdByTicket?ticket="+ticket);

        // 返回ticket对应的协同登录名
        return loginName;
    }
    public void logoutNotify(String ticket) {
        HttpClient hc = new HttpClient();
        //用户退出协同时,通知第三方系统。
        loginName = hc.get("http://第三方系统/logout?ticket="+ticket);
    }
}
  1. 建立握手类Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="byName">
<bean id="samplesso" class="com.seeyon.ctp.portal.sso.SSOLoginContext">
    <property name="name" value="sample"/>
    <property name="forward" value="true"/>
    <property name="handshake">
        <!-- 使用自己的握手实现 -->
        <bean class="com.seeyon.apps.plugin.MySSOLoginHandshake" />
    </property>
</bean>
</beans>

# 实现原理

从第三方系统登录到协同,这种认证是完全信任第三方系统的,单点登录有两种效果:

  • 单点登录成功后直接打开协同主页面(缺省模式)。
  • 单点登录成功后并不打开协同主页面,平台维护ticket信息和登录用户信息,为以后请求服务作认证使用。

比如:请求获得协同待办事项列表服务。需要配置SSOLoginContext.xml中一个属性如下: 单点登录的前提是外部系统使用和协同一致的登录名或维护了两个系统的登录名映射,也就是说,外部系统必须知道它的当前用户在协同中的登录名。 请注意,为了安全,登录过程必须由第三方系统的服务器发出,不允许第三方系统客户端直接单点登录。 登录过程:

1696765439319.png

  1. 获取当前用户的ticket:

    用户登录外部平台,发出要访问协同平台的请求。

  2. 生成ticket:

    外部平台生成ticket,ticket不能为中文,也不能使用协同平台的登录名,最理想的ticket是类似sessionId的随机字符串。并在第三方系统服务器中,记录 ticket 和 ctp平台中的loginName的关联关系。

  3. 登录:外部平台带着ticket、from(握手bean的name,如下文中的gke)和UserAgentFrom跳转到协同平台

    UserAgentFrom是一个枚举代表着不同端的登录(默认值:pc)。
    
     UserAgentFrom枚举值如下:
    
         pc     //pc端
         phone    //移动端
         ucpc    //ucpc
         wechat    //微信端
    
     登录URL如下所示:
    
     /seeyon/login/sso?from=gke&ticket=ticket***&UserAgentFrom=pc
    
  4. handshark 握手请求:

    发起一个请求,从第三方系统中获取ticket对应的loginName。

  5. 返回ticket对应loginName:

    外部平台将ticket对应的协同登录名告知协同协同平台。如果没有获取到loginName,表示握手失败。

  6. SSOOK:

    平台执行其他相关操作,完成ticket与用户信息绑定的注册(并在内存中存储ticket和username映射)。在response header中增加SSOOK。

  7. 返回ticket

  8. 协同平台通过ticket参数,调用登录操作,完成用户登录

    示例:
         /seeyon/main.do?method=login&ticket=[ticket]&ssoFrom=gke&UserAgentFrom=pc
    
  9. 定时发送心跳,保证用户在线

    各个端的心跳接口如下:

     通用心跳接口(保持当前用户登录的端的心跳):/seeyon/getAJAXOnlineServlet?V=[随机数]
     致信端心跳接口:/seeyon/uc/rest.do?method=showUpdateSystemHistoryMessages&timestamp=[随机数]
     M3端心跳接口: /seeyon/rest/m3/message/classification
    

退出过程:

  • 用户退出协同时,会通过SSOLoginHandshakeInterface的logoutNotify()通知到第三方系统
  • 用户退出第三方系统时,访问协同平台的/seeyon/login/ssologout?from=&ticket=&userAgentFrom通知协同平台 !!!重要,为了安全,必须logout。

接口说明:

  • 外部平台需要有协同登录名的映射表或者使用相同的登录名
  • SSOLoginHandshakeInterface与from映射,需要配置或二次开发。
  • 配置:系统提供通用的com.seeyon.ctp.portal.sso.SSOLoginHandshakeServletImpl,配置url(外部系统中传入ticket返回协同登录名的页面地址)和logoutUrl即可。 二次开发:实现接口SSOLoginHandshakeAbstract,自己编写握手逻辑,比如下面的GKEA8SSOLoginImpl。
  • 平台的验证系统完全依赖和信任第三方系统(存在风险,因此,只在服务器之间握手至关重要)

配置文件说明:在插件的spring目录增加xml配置文件

SSOLoginContext.xml示例:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="byName">
<bean id="gke" class="com.seeyon.ctp.portal.sso.SSOLoginContext">
    <property name="name" value="gke"/>
    <!-- 单点登录成功是否跳转到首页 -->
    <property name="forward" value="false"/>
    <!-- 握手配置 -->
    <property name="handshake">
        <!-- 使用系统实现的缺省握手类 -->
        <bean class="com.seeyon.ctp.portal.sso.SSOLoginHandshakeServletImpl">
            <!-- 第三方系统页面,传入ticket,返回协同登录名 -->
            <property name="url" value="http://第三方系统:8080/checkTicket"/>
            <!-- 第三方系统页面单点登录登出地址 -->
            <property name="logoutUrl" value="http://第三方系统:8080/ssologout"/>
        </bean>
    </property>
</bean>
</beans>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans default-autowire="byName">
<bean id="gke" class="com.seeyon.ctp.portal.sso.SSOLoginContext">
    <property name="name" value="gke"/>
    <property name="handshake">
        <!-- 使用自己的握手实现 -->
        <bean class="com.seeyon.v3x.plugin.gke.sso.GKEA8SSOLoginImp" />
    </property>
</bean>
</beans>

二次开发需要实现SSOLoginHandshakeAbstract的handshake和logoutNotify方法

/**
* 通过握手获取平台的认证信息
* @param ticket 平台传过来的令牌信息
* @return 返回当前登录者的登录名
*/
public String handshake(String ticket);

/**
 * A8退出时通知外部平台
 * 
 * @param ticket 平台传过来的令牌信息
 */
public void logoutNotify(String ticket);

代码示例

从GKE登录到协同平台:

public class GKEA8SSOLoginImp  extends SSOLoginHandshakeAbstract {
    // “ticket” 就是ticket取得的参数值
    public String handshake(String ticket) {
        if(ticket==null||ticket.equals("")) {
            return null;
        }
        String userName="";
        // 这个是gke自己特定的ticket格式:{GID},{passport}
        String[] r=ticket.split(",");
        if(r==null||r.length!=2) {
            return null;
        }
        userName=this.checkPassport(r[0], r[1]);
        return userName;
    }
    public void logoutNotify(String ticket) {

    }

    private String checkPassport(String GID,String passPort) {
        StringBuffer sb = new StringBuffer();
        sb.append("<?xml version="1.0" encoding="UTF-8" ?>");
        sb.append("<request type="login" subtype="passport" msid="">");
        sb.append("<message>");
        sb.append("<user");
        sb.append(" GID=""+GID+""  gid=""  zoneid="">");
        sb.append("<passport>");
        sb.append(passPort);
        sb.append("</passport>");
        sb.append("</user>");
        sb.append("</message>");
        sb.append("</request>");  
        // 向GKE发出请求,取得协同登录名
        return getGKEResponse(postGKERequest(sb.toString()));
}

# 单点登录的URL包装

单点登录成功获取Ticket以后,要想使用ticket访问协同平台内部地址,必须对URL进行包装

String ticket = request.getParameter("ticket");
String url="collaboration/collaboration.do?method=summary&openFrom=listPending&affairId=-932843950278492";
TicketInfo ticketInfo = com.seeyon.ctp.portal.sso.SSOTicketBean.getTicketInfo(ticket);
if(ticketInfo != null) {
  response.sendRedirect(com.seeyon.ctp.portal.sso.SSOTicketBean.makeURLOfSSOTicket(ticket, url));
}

# 示例:单点登录+事项列表场景

  1. 通过单点登录接口的tourl参数,直接登录OA并且打开tourl对应OA地址页面。

    完整示例:

    OA中某待办协同地址:
    http://ip:端口/seeyon/collaboration/collaboration.do?method=summary&openFrom=listPending&affairId=-1036595126906172786
    对应单点登录URL:
    http://ip:端口/seeyon/login/sso?from=sample&ticket=lsm1&UserAgentFrom=pc&tourl=%2fseeyon%2fcollaboration%2fcollaboration.do%3Fmethod%3Dsummary%26openFrom%3DlistPending%26affairId%3D-1036595126906172786
    

    从上面的示例中可以看出tourl是OA中具体链接的【/seeyon/collaboration/collaboration.do?method=summary&openFrom=listPending&affairId=-1036595126906172786】部分做了转码。

  2. 通过事项接口获取列表URL后直接打开OA地址页面。

    看了上面的示例可能会有人问如何获取OA中具体协同的打开地址呢?

    现在OA平台接口有2种方式可以获取事项URL。

    第一种方式:SOAP接口的exportPendingList方法:获取待办事项列表。这个接口需要要单点登录成功以后才会导出事项 URL地址。

    第二种方式:REST接口中【门户集成】-【事项接口】(注意在V7.0版本,事项接口才可以导出URL地址),可以获取事项的URL地址。

    注意:通过以上两种方式获取到URL地址以后,必须要单点登录成功以后,才能再请求获取到的URL地址。而如果是通过tourl方式打开URL,则是单点登录与打开URL同时请求,不需要单独再做单点登录请求。

其他

部署说明:单点登录的开发建立的java文件放在com.seeyon.ctp.portal.sso包下

跳转到协同页面

将协同页面集成为第三方系统菜单项:

单点登录以后,如果要实现在第三方系统打开协同平台任意页面的功能,可以使用下面的跳转链接:

/seeyon/a8genius.do?method=window&url=xxx

例如:

/seeyon/a8genius.do?method=window&url=calEvent.do%3Fmethod%3DhomeEntry

可以跳转到协同平台的日程计划页面/seeyon/calEvent.do?method= homeEntry 如果不进行跳转,协同平台页面可能找不到依赖的javascript脚本,导致脚本错误,无法使用。

编撰人:lichaoj