SSO

发布时间 2023-07-18 17:39:29作者: 水木夏

简介

概念介绍

什么是单点登录?

单点登录全称 Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分。

比如,一个项目使用了多个产品,每个产品有各自的登录用户名、密码,如果使用每个产品,都要输入用户、密码,这对使用者来说,肯定是比较痛苦的。
而现在,有个SSO技术,只需使用一套用户名及密码,就可以自动登录/登出任一产品,这样是不是方便多了呢?

单点登录≠门户。

门户系统:其主要框架为 IAM系统平台(Identity and Access Management 的缩写),即“身份识别与访问管理”,具有单点登录、强大的认证管理、基于策略的集中式授权和审计、动态授权、企业可管理性等功能。

其重点便在于身份管理,而单点登录,是其中一个重要的功能。单点登录在一个集中化系统中所起的作用,就是用户访问业务系统时所进行的身份认证

技术架构

SSO 从应用架构层面,主要可以分为集中验证模式和多点验证模式。

  1. 多点验证模式

    应用系统提供各自的登录界面,登录了一套系统后,另外其他系统无需再次登录即可通过身份验证。

    在多点验证模式的模式下,所有的登录操作都在应用系统完成,任何一套系统宕机不会对其它系统产生影响,也不会影响正常运行系统间的SSO。但若各套系统的账户不一样的时候,若要用户区分每套系统的用户密码,必定会降低用户的体验,为了解决该问题,多点登录模式最好有统一的用户密码验证的服务(如LDAP身份验证)。

  2. 集中验证模式(中国移动项目的单点登录、汉得各个服务平台的单点登录)

    当应用系统需要登录时,统一交由验证服务器完成登录动作,应用系统不提供登录接入(如登录界面)

    相对于多点验证模式来说,集中验证模式的适用范围更广,而且在SSO服务器中使用的是统一的用户名密码,用户无需关注登录的是哪套系统,所以用户体验更加优秀。由于所有的登录都放在了统一的服务器,所以当集中验证服务器宕机时,所有系统无法正常登录,或丢失SSO的功能,建议以独立服务器作为集中验证服务器,并需要保证登录服务器的稳定性。

技术实现

  1. 代理登录(agent)

    代理登录的原理就是在IE端通过表单提交的方式模拟应用系统的登录操作,实现SSO。

    优点:无需对原有系统做任何改造,适用于无法改造的旧系统;

    缺点

    • 稳定性差,一旦登录期间某台服务器无法响应,则该服务器无法单点登录;

    • 安全性差,用户名密码通过明文传输;

    • 由于登录期间需要监控各个系统的响应,所以不建议大量使用,否则会影响 登录的性能;

    • 由于IE的安全限制,代理登录必须在同域的情况下运行。

  2. 令牌环(token)

    通过Cookie 共享令牌环的方式传递当前用户信息,实现SSO。(如 IBM 的LTPA Token,IBM 系列产品间能实现配置式SSO,就依靠此技术。如 IBM Websphere Portal Server 与 Lotus Domino Server 之间的SSO)。

    优点:无需统一的验证服务器,是“多点验证模式”的主力实现技术,各个服务器都通过统一的密钥对令牌进行加密解密,安全性高、稳定性好、性能消耗低;

    缺点:必须保证各台应用服务器同域;

  3. 身份票据(ticket)

    与令牌环不一样,身份票据是通过URL的方式传递,通过“两次握手”的方式,是适用范围最广的一种SSO实现方式,

    优点:可以解决跨域等问题,安全性高、稳定性好;

    缺点:必须增加一台验证服务器,保证在高压下验证服务器的稳定运行,性能方面由于每次登录都需要访问验证服务器,所以比令牌环的方式略差一点。

CAS

CAS:Central Authentication Service,耶鲁大学开发的开源系统,使用身份票据实现

原理

image-20210108151931092

结合上图流程和CAS的结构体系来看,CAS包括两部分:CAS Server 和 CAS Client 。

  • CAS Server

    ​ 负责完成对用户的认证工作,会为用户签发两个重要的票据:登录票据(TGT)和服务票据(ST)来实现认证过程,CAS Server需要独立部署。

  • CAS Client

    ​ 负责处理对客户端受保护资源的访问请求,需要对请求方进行身份认证时,重定向到 CAS Server 进行认证。准确地来说,它以 Filter 方式保护受保护的资源。
    ​ 对于访问受保护资源的每个 Web 请求,CAS Client 会分析该请求的 Http 请求中是否包含 ServiceTicket(服务票据,由 CAS Server发出用于标识目标服务)。

    ​ CAS Client 与受保护的客户端应用部署在一起。

CAS的核心就是 Ticket,及其在Ticket之上的一系列处理操作。CAS的主要票据有TGT、ST、PGT、PGTIOU、PT,其中TGT、ST是CAS1.0(基础模式)协议中就有的票据,PGT、PGTIOU、PT是CAS2.0(代理模式)协议中有的票据。这里主要介绍CAS1.0(基础模式)中的几种票据。

  • TGT(Ticket Grangting Ticket)

    TGT是CAS为用户签发的登录票据,拥有了TGT,用户就可以证明自己在CAS成功登录过。
    TGT封装了Cookie值以及此Cookie值对应的用户信息。
    用户在CAS认证成功后,生成一个TGT对象,放入自己的缓存(Session);同时,CAS生成cookie(叫TGC),写入浏览器。TGT对象的ID就是cookie的值,当HTTP再次请求到来时,如果传过来的有CAS生成的cookie,则CAS以此cookie值(SessionId)为key查询缓存中有无TGT(Session),如果有的话,则说明用户之前登录过,如果没有,则用户需要重新登录。

  • TGC (Ticket-granting cookie)

    上面提到,CAS-Server生成TGT放入自己的Session中,而TGC就是这个Session的唯一标识(SessionId),以Cookie形式放到浏览器端,是CAS Server用来明确用户身份的凭证。(如果你理解Session的存放原理的话就很好理解)

  • ST(Service Ticket)

    ST是CAS为用户签发的访问某一服务票据。用户访问service时,service发现用户没有ST,则要求用户去CAS获取ST。用户向CAS发出获取ST的请求,如果用户的请求中包含cookie,则CAS会以此cookie值为key查询缓存中有无TGT,如果存在TGT,则用此TGT签发一个ST,返回给用户。用户凭借ST去访问service,service拿ST去CAS验证,验证通过后,允许用户访问资源。

    为了保证ST的安全性,其生产机制是随机的,没有规律性。而且,CAS规定 ST 只能存活一定的时间,然后 CAS Server 会让它失效。而且CAS 协议规定ST只能使用一次,无论 Service Ticket 验证是否成功, CASServer 都会清除服务端缓存中的该 Ticket ,从而可以确保一个 Service Ticket 不被使用两次

服务端搭建

前面说到CAS是基于身份票据的,也就是集中验证模式,我们需要搭建一个身份验证服务器。

下面的演示案例,是在本地搭建一个CAS服务器。

使用的开发工具及版本需求:

  • IDEA

  • Maven3.3.9+

  • JDK1.8+

  • tomcat8.0+

  • CAS 5.3

下载

​ 地址:https://github.com/apereo/cas-overlay-template/,选择5.3,下载zip
image-20210108155646152

创建项目

解压上一步的zip文件, 然后新建项目引入:
image-20210108160143189

image-20210108160345977 image-20210108160450657
配置

终端执行命令: mvn install
image-20210109160817252

创建服务器tomcat配置
image-20210108161115471

image-20210108161449008 image-20210108161352668
测试

启动项目,会弹出CAS登录页,表明搭建成功:

image-20210108161616526

默认用户密码:image-20210108164009064

启用HTTPS

为正常使用CAS,需启用HTTPS安全协议。

证书

启用HTTPS,需要使用证书,这里使用java自带的keytool工具,生成本地证书,供开发测试使用.

  1. 创建证书

    keytool -genkey -alias ssodemo -keyalg RSA -keysize 1024 -keypass 123456 -validity 365 -keystore ssodemo.keystore -storepass 123456
    

    image-20210108173126988

    注意这里的第一条设置,必须在本地host中配置对应条目:image-20210108173239224

  2. 导出证书

    keytool -export -alias ssodemo -keystore ssodemo.keystore -file ssodemo.crt -storepass 123456
    

    image-20210108173535977

  3. 导入JDK

    keytool -import -alias ssodemo -file ssodemo.crt -storepass changeit -keystore .../jre/lib/security/cacerts
    

    注意这里要导入的目标文件是JDK下的cacerts,路径请根据自己实际路径修改

配置tomcat

​ 修改tomcat安装目录下conf/server.xml文件:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true" scheme="https" 
               secure="true" clientAuth="false" sslProtocol="TLS"
               keystoreFile="/Users/startong/Documents/CAS/ssodemo.keystore"
               keystorePass="123456"/>

大概在八九十行:
image-20210109151916749

配置项目

与tomcat端口一致:
image-20210109160920895

启动项目测试:image-20210109161058855

密码存储-数据库

前面搭建好的项目中,只有一个默认的用户、密码,正常使用时,肯定是要有个地方存储的,这里先展示使用数据库存储的案例。

前置配置

搭建数据库,我这里使用使用MySql.

搭建过程略,我的MySql中,已建有数据库hzero_platform,表iam_user,用户名、密码:image-20210108165138347

修改项目配置
  1. 完善目录
    image-20210108165646616

    image-20210108165715457
  2. 将overlays中的application.properties拷贝过来
    image-20210108170006702

  3. 修改src/main/resources/application.propertis:

    #cas.authn.accept.users=casuser::Mellon
    cas.authn.jdbc.query[0].url=jdbc:mysql://server.strive.com:3306/hzero_platform
    cas.authn.jdbc.query[0].user=hzero
    cas.authn.jdbc.query[0].password=hzero
    cas.authn.jdbc.query[0].sql=SELECT hash_password FROM iam_user WHERE login_name=upper(?)
    #下面这个字段很关键,对应的是数据库中的字段,和上面的sql结果中的字段保持一致
    cas.authn.jdbc.query[0].fieldPassword=hash_password
    cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
    

    url: 数据库连接地址
    user: 数据库登录用户
    password: 数据库登录密码
    sql: 查询用户密码的语句

image-20210108170222101
  1. 添加依赖

    <dependency>
      <groupId>org.apereo.cas</groupId>
      <artifactId>cas-server-support-jdbc</artifactId>
      <version>${cas.version}</version>
    </dependency>
    
    <dependency>
      <groupId>org.apereo.cas</groupId>
      <artifactId>cas-server-support-jdbc-drivers</artifactId>
      <version>${cas.version}</version>
    </dependency>
    
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>
    
  2. 启动项目
    image-20210109163417934

    这里的用户、密码,与数据库中一致,密码加密,后续会有介绍。

客户端(EBS)搭建

这里使用EBS应用,作为客户端,版本为12.2.9

下载

可在maven官网下载cas-client-core:image-20210109165145628

这里选用3.2.x版本,可以测试通过。更高的3.3+版本好像不支持,可以自行测试使用。

上传

解压cas-client-core-3.2.2.jar,将META-INF 和 org文件夹上传到EBS的$OA_HTML/WEB-INF/classes。

配置template

参考官网
Using AutoConfig to Manage System Configurations in Oracle E-Business Suite Release 12 (ID 387859.1),章节 : 4.2. Implementing AutoConfig Customizations
第三小节: Customizing an AutoConfig template file delivered by Oracle

  1. 在EBS应用服务器$FND_TOP/admin/template下,创建custom文件夹

  2. 拷贝$FND_TOP/admin/template/oacore_web_xml_FMW.tmp至custom

  3. 修改custom/oacore_web_xml_FMW.tmp

    • 在context-param配置下面添加客制化过滤器,位置一定不能错
      image-20210109170904543

    • 过滤器内容:

      <!-- added by star.tong 2020-12-16 for cas sso begin -->
      	<!-- ======================== 单点登录开始 ======================== -->
      	<!-- 配置单点退出。如果此处不配置,不能单点退出,并且要放到所有过滤器的最前面 -->
      	<listener>
      		<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
      	</listener>
      	<filter>
      		<filter-name>SingleSignOutFilter</filter-name>
      		<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
      	</filter>
      	<filter-mapping>
      		<filter-name>SingleSignOutFilter</filter-name>
      		<url-pattern>/*</url-pattern>
      	</filter-mapping>
      
      	<!--该过滤器负责用户的认证工作,必须启用它 -->
      	<filter>
      		<filter-name>CASFilter</filter-name>
      		<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
      
      		<!-- CAS服务端登录地址 -->
      		<init-param>
      			<param-name>casServerLoginUrl</param-name>
      			<param-value>https://real.strive.com:8443/cas/login</param-value>
      		</init-param>
      
      		<!-- EBS登录地址 -->
      		<init-param>
      			<param-name>serverName</param-name>
      			<param-value>apptest.strive.com:8000</param-value>
      		</init-param>
      	</filter>
      
      	<filter-mapping>
      		<filter-name>CASFilter</filter-name>
      		<url-pattern>/AppsLocalLogin.jsp</url-pattern>
      	</filter-mapping>
      
      	<!--该过滤器负责对Ticket的校验工作,必须启用它 -->
      	<filter>
      		<filter-name>CAS Validation Filter</filter-name>
      		<filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
      
      		<!-- CAS服务端登录地址 -->
      		<init-param>
      			<param-name>casServerUrlPrefix</param-name>
      			<param-value>https://real.strive.com:8443/cas/</param-value>
      		</init-param>
      
      		<!-- EBS登录地址 -->
      		<init-param>
      			<param-name>serverName</param-name>
      			<param-value>apptest.strive.com:8000</param-value>
      		</init-param>
      	</filter>
      
      	<filter-mapping>
      		<filter-name>CAS Validation Filter</filter-name>
      		<url-pattern>/AppsLocalLogin.jsp</url-pattern>
      	</filter-mapping>
      
      	<!--
        该过滤器负责实现HttpServletRequest请求的包裹,
        比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。
        -->
      	<filter>
      		<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
      		<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
      	</filter>
      
      	<filter-mapping>
      		<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
      		<url-pattern>/AppsLocalLogin.jsp</url-pattern>
      	</filter-mapping>
      
      	<filter>
      		<filter-name>CAS Assertion Thread Local Filter</filter-name>
      		<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
      	</filter>
      
      	<filter-mapping>
      		<filter-name>CAS Assertion Thread Local Filter</filter-name>
      		<url-pattern>/AppsLocalLogin.jsp</url-pattern>
      	</filter-mapping>
      
      	<!-- added by star.tong 2020-12-16 for cas sso end  -->
      
    • 在servlet(AuthenticateUser)下面添加客制化登录验证过滤器:
      image-20210109171422655

      <!-- Customer for ad login ebs begin -->
      <filter>
        <filter-name>ValidUserFilter</filter-name>
        <filter-class>hand.oracle.apps.ad.ValidUserFilter</filter-class>
      </filter>
      
      <filter-mapping>
        <filter-name>ValidUserFilter</filter-name>
        <url-pattern>/AppsLocalLogin.jsp</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>INCLUDE</dispatcher>
      </filter-mapping>
      <!-- Customer for ad login ebs end -->
      

安装客制化代码

  • 将java代码文件夹hand上传到$OA_HTML/WEB-INF/classes
  • 安装客制化程序包cux_ad_login_pkg

导入证书

  1. 找到JDK路径:cat $CONTEXT_FILE | grep -i s_fmw_jdktop

  2. 上传ssodemo.crt文件至EBS服务器

  3. 导入证书

    keytool -import -alias ssodemo -file ssodemo.crt -storepass changeit -keystore <jdk_path>/jre/lib/security/cacerts
    

修改登出

  • 在$OA_HTME下创建登出页面cux_fnd_sso_logout.html:

    <!DOCTYPE html>
    <html>
    
    <head>
        <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    </head>
    <script>
        hostname = window.location.host;
        window.location.href = "https://real.strive.com:8443/cas";
    </script>
    
    <body id="cas">
        <div class="alert alert-success">
            <br>
            <p>单点登出跳转页面,请<a href="https://real.strive.com:8443/cas">
                    <font size="3" color="red"><b>登出</b></font>
                </a></p>
        </div>
    
        <div class="foot"
            style="margin-top: 50px; width: 100%; text-align: center; position: fixed; bottom: 20px; left: 0px;">
            <div class="clearfloat"></div>
            <div class="copyright" style="width: 100%"></div>
        </div>
    </body>
    
    </html>
    
  • 修改$OA_HTML/OALogout.jsp(记得备份)
    image-20210109173005678

    response.sendRedirect("http://apptest.strive.com:8000/OA_HTML/cux_fnd_sso_logout.html");
    

    注释原来的跳转,添加新的跳转至上一步创建的页面。
    不要直接跳转到CAS服务端地址,要通过EBS自己的页面,否则会跳转失败。

生效配置

  • 添加证书host配置:
    image-20210111092926346

  • 编译OALogout.jsp

    $FND_TOP/patch/115/bin/ojspCompile.pl --compile --flush -s OALogout.jsp
    
  • 执行AutoConfig

  • 重启oacore

至此完成修改,再次登录EBS地址时,会跳转到CAS服务器端,登录后自动跳回EBS系统!

启用HTTP

添加依赖

<dependency>
  <groupId>org.apereo.cas</groupId>
  <artifactId>cas-server-support-json-service-registry</artifactId>
  <version>${cas.version}</version>
</dependency>

添加文件

拷贝overlay下的services文件及,到resource下:
image-20210120152446632

修改第二个.json文件,添加http:
image-20210120152536395

修改application.properties文件,添加:

###### 启用HTTP
#此配置不同于5.1,改了个名字
cas.serviceRegistry.json.location=classpath:/services
#让cas的票据安全性设置为false,控制是否在安全的情况下在浏览器中传递票据,
#如果不设置false,那么就无法做到多个应用只登陆一次的效果。
cas.tgc.secure=false