【分布式技术专题】「单点登录技术架构」一文带领你好好对接对应的Okta单点登录实现接口服务的实现落地

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 【分布式技术专题】「单点登录技术架构」一文带领你好好对接对应的Okta单点登录实现接口服务的实现落地

什么是SAML协议

SAML(Security Assertion Markup Language)是一种基于XML的标准,用于在不同的安全域之间传递身份验证和授权数据。SAML2.0是SAML协议的最新版本,它提供了一种标准的方式来实现单点登录(SSO)和跨域身份验证(Cross-Domain Authentication)。



SAML2.0协议定义了三种角色:身份提供者(Identity Provider,IdP)服务提供者(Service Provider,SP)用户(User) 。其中,身份提供者 是负责认证用户身份的系统,服务提供者是提供服务的系统,用户是需要访问服务的个体。

SAML 2.0协议的流程



  1. 用户向服务提供者发起请求,服务提供者检查用户是否已经登录。
  2. 如果用户没有登录,则服务提供者将用户重定向到身份提供者。
  3. 身份提供者验证用户身份,并生成一个SAML响应,其中包含用户的身份信息和授权信息。
  4. 身份提供者将SAML响应发送给服务提供者。
  5. 服务提供者验证SAML响应的签名,并提取用户的身份信息和授权信息。
  6. 服务提供者使用用户的身份信息和授权信息来授权用户访问服务。

SAML2.0协议的优点

SAML 2.0协议的优点在于它提供了一种标准的方式来实现跨域身份验证和单点登录,从而简化了用户的登录流程,提高了用户体验。此外,SAML 2.0协议还提供了强大的安全性和灵活性,可以满足各种不同的安全需求。

SAML2.0协议的特点

  • 可用性:从门户或Intranet的一键式访问,深层链接,消除密码和自动续订会话,使用户的生活更轻松。
  • 安全性:SAML基于强大的数字签名进行身份验证和完整性,是世界上最大,最具安全意识的企业所依赖的安全单点登录协议。(网页仿冒防范-如果您没有应用程序密码,就不会被欺骗在虚假的登录页面上输入密码
  • 速度:SAML速度很快。

总之,SAML 2.0协议是一种重要的安全标准,它可以帮助企业实现跨域身份验证和单点登录,提高用户体验和安全性。

SAML开发实战指南

建立SAML协议的SP服务提供者-服务

首先,我们需要先建立出来一个具有支持SAML协议的认证的SP(服务提供者)。使用Java工具包使我们可以将Java应用程序转换为可以连接到IdP(身份提供者)的SP(服务提供者)

SP需要支持功能和特性:

  • SSO(单点登录)和SLO(单点登出)
  • SSO:由SP发起
  • SLO:由IdP发起
  • 解析和分析断言Assert和nameId加密
  • 断言签名:signature
  • 消息签名:AuthNRequest, LogoutRequest, LogoutResponses.
  • 配置断言Assert消费者服务(ACS) 端点.
  • 配置单点登出服务(SLS) 端点.
  • 发布SP metadata(可以签名)

建立SAML协议的IDP服务

我们使用oneLogin提供的IDP服务来进行开发测试SAML协议,该服务需要注册开发者账户后获取。下面将介绍如何搭建。

developers.onelogin.com/



当然你也可以通过其他第三方的IDP进行实现也可,例如Okta或者Azure服务都可以,此处暂时不做过多的赘余豁免。


开始对接实现SP服务提供者

Maven引入方式

xml

复制代码

<dependency>
    <groupId>com.onelogin</groupId>
    <artifactId>java-saml</artifactId>
    <version>2.5.0</version>
</dependency>

采用OneLogin实现Saml协议认证开发

配置对应的IDP身份的相关参数

默认需要在OneLogin的配置文件onelogin.saml.properties中配置(IDP)身份提供者参数:

SSO的标记属性 Settings的配置属性
Issuer URL onelogin.saml2.idp.entityid
SAML 2.0 Endpoint (HTTP) onelogin.saml2.idp.single_sign_on_service.url
SLO Endpoint (HTTP) onelogin.saml2.idp.single_logout_service.url
X.509 Certificate > View Details onelogin.saml2.idp.x509cert

主要面向的就是配置onelogin.saml.properties的“ idp”(身份提供者,以onelogin.saml2.idp开头的参数)部分。

配置对应的SP身份的相关参数

在OneLogin中定义(SP)服务提供商的参数

SSO的标记属性 Settings的配置属性
Audience onelogin.saml2.sp.entityid
Single Logout URL onelogin.saml2.sp.single_logout_service.url
Recipient onelogin.saml2.sp.assertion_consumer_service.url
ACS (Consumer) URL onelogin.saml2.sp.assertion_consumer_service.url
RelayState 参数可以不用填写 在发起sso代码处可以指定

IDP端的配置可以实时修改保存(在 正式项目环境中我们通常 用metadata文件来进行配置交互)

部署运行SP项目

对于onelogin.saml2.sp.nameid格式,将unspecified更改为emailAddress.在正式项目中根据实际情况来进行配置,这里是目前是OneLogin使用的值。

bash

复制代码

onelogin.saml2.sp.entityid = http://localhost:8080/metadata.jsp
onelogin.saml2.sp.assertion_consumer_service.url = http://localhost:8080/acs.jsp
onelogin.saml2.sp.single_logout_service.url = http://localhost:8000/sls.jsp

保存配置后进入onelogin连接器的配置选项卡,然后将值从onelogin.saml.properties复制到``配置’’选项卡字段中,如下所示。

onelogin.saml.properties配置文件解读

SP端配置

properties

复制代码

# 协议配置 基本上默认 没有特殊修改
# SP entityId
#  Identifier of the SP entity  (must be a URI)
onelogin.saml2.sp.entityid = http://localhost:8080/metadata.jsp
# SP 断言解析服务地址
# Specifies info about where and how the <AuthnResponse> message MUST be
#  returned to the requester, in this case our SP.
# URL Location where the <Response> from the IdP will be returned
onelogin.saml2.sp.assertion_consumer_service.url = http://localhost:8080/acs.jsp
# SAML protocol binding to be used when returning the <Response>
# message.  Onelogin Toolkit supports for this endpoint the
# HTTP-POST binding only
onelogin.saml2.sp.assertion_consumer_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST
#单点登出 服务地址 主要是提供给 IDP端用于接收登出响应的
# Specifies info about where and how the <Logout Response> message MUST be
# returned to the requester, in this case our SP.
onelogin.saml2.sp.single_logout_service.url = http://localhost:8080/sls.jsp
# SAML protocol binding to be used when returning the <LogoutResponse> or sending the <LogoutRequest>
# message.  Onelogin Toolkit supports for this endpoint the
# HTTP-Redirect binding only
onelogin.saml2.sp.single_logout_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
# nameID 格式 一般使用 unspecified 默认参数 
# Specifies constraints on the name identifier to be used to
# represent the requested subject.
# Take a look on lib/Saml2/Constants.php to see the NameIdFormat supported
onelogin.saml2.sp.nameidformat = urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
# sp端用于报文加密签名使用到的证书
# 这里我们可以自签的x509格式证书 也可以使用pem格式通过 以下方式转换(我使用的是https证书 other下载而来的)
# 公钥转换 openssl x509 -in x509cert.pem -text -out Cert.pem
# 私钥转换 openssl pkcs8 -topk8 -inform pem -nocrypt -in sp.rsa_key -outform pem -out sp.pem
# 证书粘贴注意不要前后注释 并保持在一行上 证书解析对空格换行符敏感
# Usually x509cert and privateKey of the SP are provided by files placed at
# the certs folder. But we can also provide them with the following parameters
onelogin.saml2.sp.x509cert =
# Requires Format PKCS#8   BEGIN PRIVATE KEY       
# If you have     PKCS#1    convert it by   openssl pkcs8 -topk8 -inform pem -nocrypt -in sp.rsa_key -outform pem -out sp.pem
onelogin.saml2.sp.privatekey =

IDP端配置

properties

复制代码

# IDP  entityId
# Identifier of the IdP entity  (must be a URI)
onelogin.saml2.idp.entityid = https://app.onelogin.com/saml/metadata/2edb5038-be70-40f5-ad3b-2de9d00ab1a3
# SSO endpoint info of the IdP. (Authentication Request protocol)
# URL Target of the IdP where the SP will send the Authentication Request Message
onelogin.saml2.idp.single_sign_on_service.url = https://westinfosoft-dev.onelogin.com/trust/saml2/http-post/sso/2edb5038-be70-40f5-ad3b-2de9d00ab1a3
# SAML protocol binding to be used when returning the <Response>
# message.  Onelogin Toolkit supports for this endpoint the
# HTTP-Redirect binding only
onelogin.saml2.idp.single_sign_on_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
# SLO endpoint info of the IdP.
# URL Location of the IdP where the SP will send the SLO Request
onelogin.saml2.idp.single_logout_service.url =https://westinfosoft-dev.onelogin.com/trust/saml2/http-redirect/slo/1095020
# Optional SLO Response endpoint info of the IdP.
# URL Location of the IdP where the SP will send the SLO Response. If left blank, same URL as onelogin.saml2.idp.single_logout_service.url will be used.
# Some IdPs use a separate URL for sending a logout request and response, use this property to set the separate response url
onelogin.saml2.idp.single_logout_service.response.url =
# SAML protocol binding to be used when returning the <Response>
# message.  Onelogin Toolkit supports for this endpoint the
# HTTP-Redirect binding only
onelogin.saml2.idp.single_logout_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect
# 正式项目中 sp端都是从SP_metadata 中获取 的
# Public x509 certificate of the IdP
onelogin.saml2.idp.x509cert =
# 以下是 指纹模式的配置 不过官方不建议使用 应为hash碰撞的问题 
# Instead of use the whole x509cert you can use a fingerprint
# (openssl x509 -noout -fingerprint -in "idp.crt" to generate it,
# or add for example the -sha256 , -sha384 or -sha512 parameter)
#
# If a fingerprint is provided, then the certFingerprintAlgorithm is required in order to
# let the toolkit know which Algorithm was used. Possible values: sha1, sha256, sha384 or sha512
# 'sha1' is the default value.
# onelogin.saml2.idp.certfingerprint = 3E:3B:0D:FA:F2:80:B2:0E:95:46:36:07:9A:78:BD:04:CC:76:CE:A8
# onelogin.saml2.idp.certfingerprint_algorithm = sha1
# Security settings
#安全配置 在演示项目中使用不多 不过在正式环境中 需要注意开启对应的加密项
# Indicates that the nameID of the <samlp:logoutRequest> sent by this SP
# will be encrypted.
onelogin.saml2.security.nameid_encrypted = false
#认证请求的加密
# Indicates whether the <samlp:AuthnRequest> messages sent by this SP
# will be signed.              [The Metadata of the SP will offer this info]
onelogin.saml2.security.authnrequest_signed = false
#登出请求的加密
# Indicates whether the <samlp:logoutRequest> messages sent by this SP
# will be signed.
onelogin.saml2.security.logoutrequest_signed = false
#登出响应的加密
# Indicates whether the <samlp:logoutResponse> messages sent by this SP
# will be signed.
onelogin.saml2.security.logoutresponse_signed = false
# Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest> and
# <samlp:LogoutResponse> elements received by this SP to be signed.
onelogin.saml2.security.want_messages_signed = false
# Indicates a requirement for the <saml:Assertion> elements received by this SP to be signed.
onelogin.saml2.security.want_assertions_signed = false
# Indicates a requirement for the Metadata of this SP to be signed.
# Right now supported null (in order to not sign) or true (sign using SP private key) 
onelogin.saml2.security.sign_metadata =
# Indicates a requirement for the Assertions received by this SP to be encrypted
onelogin.saml2.security.want_assertions_encrypted = false
# Indicates a requirement for the NameID received by this SP to be encrypted
onelogin.saml2.security.want_nameid_encrypted = false
# Authentication context.
# Set Empty and no AuthContext will be sent in the AuthNRequest
# You can set multiple values (comma separated them)
onelogin.saml2.security.requested_authncontext = urn:oasis:names:tc:SAML:2.0:ac:classes:Password
# Allows the authn comparison parameter to be set, defaults to 'exact'
onelogin.saml2.security.onelogin.saml2.security.requested_authncontextcomparison = exact

SP端代码

keyStores

Auth构造函数支持从KeyStore读取SP公共证书/私钥的功能。必须为KeyStoreSettings对象提供KeyStore,别名和KeyEntry密码。

java

复制代码

String keyStoreFile = "oneloginTestKeystore.jks";
String alias = "keywithpassword";
String storePass = "changeit";
String keyPassword = "keypassword";
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(keyStoreFile), storePass.toCharArray());
KeyStoreSettings keyStoreSettings =  new keyStoreSettings(ks, alias, keyPassword);
Auth auth = new Auth(KeyStoreSettings keyStoreSetting);
动态配置

您可以从其他来源(例如文件,数据库或生成的值)加载值。SettingsBuilder类公开了fromValues(Map  samlData)方法,该方法可让您动态构建设置,密钥字符串与属性文件中的相同。

java

复制代码

Map<String, Object> samlData = new HashMap<>();
samlData.put("onelogin.saml2.sp.entityid", "http://localhost:8080/java-saml-tookit-jspsample/metadata.jsp");
samlData.put("onelogin.saml2.sp.assertion_consumer_service.url", new URL("http://localhost:8080/java-saml-tookit-jspsample/acs.jsp"));
samlData.put("onelogin.saml2.security.want_xml_validation",true);
samlData.put("onelogin.saml2.sp.x509cert", myX509CertInstance);
SettingsBuilder builder = new SettingsBuilder();
Saml2Settings settings = builder.fromValues(samlData).build();
//实例化您编写的Auth类
Auth auth = new Auth(settings, request, response);

发起单点登录

用于向IDP发送AuthNRequest

java

复制代码

Auth auth = new Auth(request, response);
auth.login();

AuthNRequest将根据安全设置“ onelogin.saml2.security.authnrequest_signed”以签名或未签名的形式发送。然后,IdP将把SAML响应返回给用户的客户端。然后,使用此信息将客户端转发到SP的属性消费者服务。我们可以为登录函数设置一个“ returnTo” URL参数,并将其转换为“ RelayState”参数:

java

复制代码

//指定登录成功后跳转的地址
String targetUrl = "https://github.com/onelogin/java-saml";
auth.login(targetUrl);

SP 端点

三个重要的端点,sp元数据(SP_metadata),ACS(断言解析服务),SLS(单点登出响应解析服务)

SP Metadata

这段代码将根据设置文件中提供的信息提供SP的XML元数据文件。

java

复制代码

Auth auth = new Auth();
Saml2Settings settings = auth.getSettings();
String metadata = settings.getSPMetadata();
List<String> errors = Saml2Settings.validateMetadata(metadata);
if (errors.isEmpty()) {
   out.println(metadata);
} else {
   response.setContentType("text/html; charset=UTF-8");
   for (String error : errors) {
       out.println("<p>"+error+"</p>");
   }
}

Attribute Consumer Service(ACS)

此代码处理IdP通过用户客户端转发到SP的SAML响应。

java

复制代码

Auth auth = new Auth(request, response);
//具体的 响应断言解析
auth.processResponse();
if (!auth.isAuthenticated()) {
   out.println("Not authenticated");
}
List<String> errors = auth.getErrors();
if (!errors.isEmpty()) {
    out.println(StringUtils.join(errors, ", "));
    if (auth.isDebugActive()) {
        String errorReason = auth.getLastErrorReason();
        if (errorReason != null && !errorReason.isEmpty()) {
            out.println(auth.getLastErrorReason());
        }
    }
} else {
    Map<String, List<String>> attributes = auth.getAttributes();
    String nameId = auth.getNameId();
    String nameIdFormat = auth.getNameIdFormat();
    String sessionIndex = auth.getSessionIndex();
    String nameidNameQualifier = auth.getNameIdNameQualifier();
    String nameidSPNameQualifier = auth.getNameIdSPNameQualifier();
    //关键参数
    session.setAttribute("attributes", attributes);
    session.setAttribute("nameId", nameId);
    session.setAttribute("nameIdFormat", nameIdFormat);
    session.setAttribute("sessionIndex", sessionIndex);
    
    session.setAttribute("nameidNameQualifier", nameidNameQualifier);
    session.setAttribute("nameidSPNameQualifier", nameidSPNameQualifier);
    String relayState = request.getParameter("RelayState");
    if (relayState != null && relayState != ServletUtils.getSelfRoutedURLNoQuery(request)) {
        response.sendRedirect(request.getParameter("RelayState"));
    } else {
        if (attributes.isEmpty()) {
            out.println("You don't have any attributes");
        }
       else {
            Collection<String> keys = attributes.keySet();
            for(String name :keys){
                out.println(name);
                List<String> values = attributes.get(name);
                for(String value :values) {
                    out.println(" - " + value);
                }
            }
        }
    }
}

在尝试获取属性之前,请检查用户是否已通过身份验证。如果用户未通过身份验证,则将返回一个空的Map。例如,如果我们在auth.processResponse之前调用getAttributes,则getAttributes()将返回一个空Map。

Single Logout Service (SLS)

以下代码处理注销请求和注销响应。

java

复制代码

Auth auth = new Auth(request, response);
auth.processSLO();
List<String> errors = auth.getErrors();
if (errors.isEmpty()) {
   out.println("Sucessfully logged out");
} else {
   for(String error : errors) {
      out.println(error);
   }
}

如果SLS端点收到注销响应,则该响应将得到验证,并且HttpRequest的会话可能会关闭。如果SLS端点接收到注销请求,则该请求将得到验证,会话将关闭,并且注销响应将发送到IdP的SLS端点。如果我们不希望该processSLO破坏会话,则将keepLocalSession参数作为true传递给processSLO方法。

2.发起单点登出

用于发送Logout Request到IdP

注意:此方式 是通过SP端发起的单点登出

java

复制代码

Auth auth = new Auth(request, response);
String nameId = null;
if (session.getAttribute("nameId") != null) {
    nameId = session.getAttribute("nameId").toString();
}
String nameIdFormat = null;
if (session.getAttribute("nameIdFormat") != null) {
    nameIdFormat = session.getAttribute("nameIdFormat").toString();
}
String nameidNameQualifier = null;
if (session.getAttribute("nameidNameQualifier") != null) {
    nameIdFormat = session.getAttribute("nameidNameQualifier").toString();
}
String nameidSPNameQualifier = null;
if (session.getAttribute("nameidSPNameQualifier") != null) {
    nameidSPNameQualifier = session.getAttribute("nameidSPNameQualifier").toString();
}
String sessionIndex = null;
if (session.getAttribute("sessionIndex") != null) {
    sessionIndex = session.getAttribute("sessionIndex").toString();
}
auth.logout(null, nameId, sessionIndex, nameIdFormat);

将根据安全设置“ onelogin.saml2.security.logoutrequest_signed”以签名或未签名的形式发送注销请求。IdP将通过用户客户端将注销响应返回到SP的单一注销服务。

相关文章
|
10天前
|
Kubernetes 持续交付 Docker
现代后端开发中的微服务架构与容器化技术
本文探讨了现代后端开发中微服务架构与容器化技术的重要性和应用。微服务架构通过服务的拆分和独立部署提升了系统的灵活性和可维护性,而容器化技术则为微服务的快速部署和管理提供了解决方案。文章深入分析了微服务架构的优势、挑战以及如何利用容器化技术来支持微服务架构的实现。最后,通过实际案例展示了微服务与容器化技术在提升应用开发效率和系统稳定性方面的应用实践。【7月更文挑战第5天】
|
3天前
|
消息中间件 Java 开发者
Spring Cloud微服务框架:构建高可用、分布式系统的现代架构
Spring Cloud是一个开源的微服务框架,旨在帮助开发者快速构建在分布式系统环境中运行的服务。它提供了一系列工具,用于在分布式系统中配置、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等领域的支持。
20 5
|
2天前
|
Kubernetes 监控 Docker
现代后端开发中的微服务架构与容器化技术
传统的单体应用架构在面对现代大规模应用需求时已显不足,微服务架构及其伴随的容器化技术因其灵活性和可伸缩性成为了主流选择。本文探讨了微服务架构的优势及其与传统架构的对比,详细分析了容器化技术如何支持微服务的部署与管理,以及实际应用中的最佳实践。 【7月更文挑战第13天】
6 2
|
5天前
|
运维 Kubernetes 开发者
现代后端开发中的微服务架构与容器化技术
在当今快速发展的软件开发领域中,微服务架构和容器化技术日益成为开发者关注的焦点。本文将探讨微服务架构的优势、常见的容器化解决方案以及它们如何共同推动后端开发的现代化进程。 【7月更文挑战第9天】
16 5
|
5天前
|
运维 Kubernetes 开发者
现代后端开发中的微服务架构与容器化技术
本文探讨了现代后端开发中微服务架构与容器化技术的重要性及其应用。微服务架构通过将复杂的应用拆分为独立的服务单元,提升了系统的可扩展性和灵活性。容器化技术(如Docker和Kubernetes)则为微服务的部署与管理提供了高效的解决方案,极大地简化了开发者的工作流程。文章还分析了微服务与容器化技术的优势、挑战以及实际应用场景,旨在帮助开发者更好地理解和应用这些技术,提升软件开发的效率和质量。 【7月更文挑战第9天】
|
7天前
|
消息中间件 监控 Java
使用Kafka实现分布式事件驱动架构
使用Kafka实现分布式事件驱动架构
|
3天前
|
前端开发 Linux Shell
技术心得:基于AR9331(MIPS架构)分析系统启动过程(uboot)
技术心得:基于AR9331(MIPS架构)分析系统启动过程(uboot)
|
3天前
|
存储 SQL 关系型数据库
技术好文:TiDB架构及设计实现
技术好文:TiDB架构及设计实现
10 0
|
3天前
|
NoSQL 安全 Java
技术好文:Redis分布式锁的正确实现方式
技术好文:Redis分布式锁的正确实现方式
|
10天前
|
运维 API 开发者
后端技术演进:从单体应用到微服务架构的转变
在数字时代的洪流中,后端技术的演进标志着软件开发的重大转变。本文将探讨如何从传统的单体应用过渡至微服务架构,这一过程涉及的不仅是代码层面的重构,更是对开发、部署和运维模式的根本变革。我们将深入分析微服务架构带来的优势与挑战,并讨论如何在保持系统稳定性的同时实现平滑过渡。通过具体案例,本文旨在为读者提供一套清晰的指南,帮助他们在面对日益复杂的业务需求时,能够有效地采用微服务架构。