单点登录(SSO)系统自建方案总结
一篇文章带你了解单点登录系统,包括不限于:SSO 是什么、SSO 的常用协议、SSO 的登录流程、SSO 的几种常见解决方案以及最后的企业级SSO 系统搭建。
什么是 SSO
单点登录(SSO)是一种身份验证解决方案,可让用户通过一次性用户身份验证登录多个应用程序和网站。这意味着用户只需输入一次用户名和密码,即可访问所有相互信任的系统,而无需在每个系统中单独登录。鉴于当今的用户经常直接从其浏览器访问应用程序,因此组织正在优先考虑改善安全性和用户体验的访问管理策略。SSO 兼具这两方面的优点,因为一旦验证身份,用户就可以访问所有受密码保护的资源,而无需重复登录。
SSO 的主要优点包括:
用户体验改进:用户只需记住一个用户名和密码,减少了重复登录的麻烦。
安全性增强:通过集中管理认证信息,可以更好地控制和保护用户数据,降低密码泄露的风险。
效率提升:减少了因多次登录导致的时间浪费,提高了工作效率。
在 SSO(单点登录)系统中,身份提供者(Identity Provider,IdP)和服务提供者(Service Provider,SP)是两个关键角色。SSO 依赖于身份提供者(Identity Provider,IdP)和服务提供者(Service Provider,SP)之间的信任关系。
身份提供者(Identity Provider, IdP):
定义:IdP 是负责验证用户身份并提供身份信息的实体。它管理用户的认证信息(如用户名、密码、令牌等)并提供这些信息给信任它的服务提供者。
例子:
企业内部 IdP:如 Microsoft Active Directory Federation Services(AD FS),它为企业内部应用提供单点登录服务。
第三方 IdP:如 Google、Facebook、Okta 等,它们为各种第三方应用提供认证服务。
服务提供者(Service Provider, SP):
定义:SP 是提供某种服务或应用的实体,它依赖 IdP 来验证用户身份。SP 信任 IdP 提供的身份验证信息,并根据这些信息决定用户的访问权限。
例子:
企业应用:如企业的邮件系统、CRM(客户关系管理)系统、ERP(企业资源计划)系统等,这些系统通过企业内部的 IdP 提供单点登录功能。
在线服务:如 Dropbox、Salesforce、Slack 等,这些在线服务可以通过第三方 IdP(如 Google 或 Okta)提供单点登录功能。
SSO 常见的协议和技术
SAML(Security Assertion Markup Language):一种基于 XML 的开放标准,用于在身份提供者和服务提供者之间交换认证和授权数据。
OAuth:一种开放标准,允许第三方应用程序在不暴露用户密码的情况下访问用户信息。
OIDC:OpenID Connect 是使用一组用户凭证访问多个站点的方法。它允许服务提供商承担验证用户凭证的角色。Web 应用程序不是将身份验证令牌传递给第三方身份提供商,而是使用 OIDC 来请求附加信息并验证用户的真实性。
LDAP:轻量级目录访问协议 (LDAP) 是一种行业标准,基于 X.500 标准的轻量级目录访问协议,用来进行统一账号管理、身份验证平台。
Kerberos:Kerberos 是一种基于票证的身份验证系统,可让两方或多方在网络上相互验证其身份。它使用安全密码学来防止未经授权访问在服务器、客户端和密钥分发中心之间传输的标识信息。
SSO 的登录流程图
详细步骤说明:
步骤 1 和 2:用户请求资源,系统 A 发现用户未登录,返回重定向指令。
步骤 3 和 4:用户浏览器重定向到 SSO,用户在 SSO 登录。
步骤 5:SSO 验证成功后,重定向回 系统 A ,并附带 token。
步骤 6:用户浏览器重定向到 系统 A 的回调页面。
步骤 7 和 8:系统 A 向 SSO 验证 token,SSO 返回用户信息。
步骤 9:系统 A 创建或更新用户登录信息。
步骤 10:系统 A 返回用户请求的资源页面。
搭建 SSO 常用解决方案
搭建一个 SSO(单点登录)系统有多种方法,具体选择取决于组织的需求、技术栈以及安全要求。以下是几种常见的方法:
1. 使用现有的 SSO 解决方案
商业解决方案:
Okta:提供广泛的 SSO 服务,包括云和本地应用的集成。
Microsoft Azure Active Directory:适用于使用 Microsoft 技术的组织,提供强大的身份管理和 SSO 功能。
Ping Identity:提供企业级的身份管理和 SSO 解决方案。
开源解决方案:
Keycloak:一个开源的身份和访问管理工具,支持 SSO、身份联合和管理。
Gluu Server:一个灵活的开源 SSO 和身份管理平台。
Shibboleth:一个支持 SAML 的开源身份提供者和服务提供者解决方案。
适用场景
快速启动:项目需要快速部署且时间紧迫。
广泛支持:需要支持多种应用和服务的集成。
企业级支持:需要企业级的技术支持和维护。
2. 自定义开发实现 SSO
SAML(Security Assertion Markup Language):
SAML 是一个基于 XML 的开放标准,用于在身份提供者和服务提供者之间交换认证和授权数据。
适用于企业级应用和内部系统集成。
OAuth 2.0 和 OpenID Connect:
OAuth 2.0 是一种授权框架,允许第三方应用在不暴露用户密码的情况下访问用户信息。
OpenID Connect 是基于 OAuth 2.0 的认证协议,提供简单而强大的身份验证功能。
适用于现代 web 和移动应用的 SSO 实现。
使用 JWT Token 自定义开发:
对于特定需求,可以自定义开发一个 SSO 系统,使用 JWT(JSON Web Token)等技术进行认证和授权。
需要较高的技术投入和安全管理。
适用场景
协议标准化:需要使用标准化协议(如 SAML、OAuth 2.0、OpenID Connect)进行认证和授权。
灵活性:需要灵活的配置和扩展能力。
定制需求:现有解决方案无法完全满足项目的特殊需求。
控制和安全:需要完全控制系统的每个方面,包括安全和性能。
3. 集成现有身份管理系统
LDAP(轻量级目录访问协议):
使用 LDAP 服务器(如 OpenLDAP、Microsoft Active Directory)集中管理用户身份,并通过 LDAP 进行认证。
适用于需要集中管理用户和组的组织。
Kerberos:
使用 Kerberos 协议进行网络认证,通过中心认证服务器验证用户身份。
适用于需要强身份验证和单点登录的企业环境。
适用场景
现有系统扩展:已有身份管理系统(如 LDAP、Active Directory)并希望扩展其功能以实现 SSO。
集中管理:希望统一管理用户身份和访问权限。
4. 使用 WAM 解决方案
Web Access Management 工具
使用 WAM 工具(如 CA SiteMinder、IBM Tivoli Access Manager)管理 web 应用的访问和 SSO。
适用于大型企业和复杂的 web 应用环境。
适用场景
复杂环境:需要复杂的访问控制和策略管理,通常用于大型企业。
多种身份验证方法:需要支持多种身份验证方法和复杂的访问策略。
假设一个业务背景:使用低成本、快速来完成 SSO 系统平台的搭建,比较推荐以下两种方式:
1、采用 SpringBoot + Oauth2 + JWT Token 技术栈,进行代码的定制化开发
2、采用开源工具(比如:Keycloak) 来快速进行搭建,在此基础上进行基础以及一定的定制化插件开发
除了低成本、快速外,我们还需要更加强大的功能,比如:支持 two-factor 双因子校验、复杂的 password strategy 等,那可以尝试使用 Keycloak 来进行快速的搭建和集成。如果你有一整套基于 Oauth2 的代码可以进行快速的复用,采用第一种方案也是一种不错的考虑,从零开始开发的话,相较于第二种方案成本会大一些。下面就来介绍一下方案二如何使用。
使用 Keycloak 搭建企业 SSO
Keycloak 是一个开源的身份和访问管理解决方案,提供了 SSO(单点登录)、多因素认证、LDAP 和 Active Directory 集成、社交登录(如 Google、Facebook 等)、用户管理等功能。它基于现代身份协议(如 OAuth 2.0、OpenID Connect 和 SAML),并且非常适合微服务架构和现代 Web 应用。
如何开始使用 KeyCloak
大致可以分为四步:
1. 安装 Keycloak:
可以使用 Docker 部署 Keycloak,或下载二进制包进行安装。
官方文档 提供详细的安装和配置指南。
2. 配置域和用户:
通过管理控制台创建和配置域、用户、角色和客户端。
3. 集成应用:
使用客户端适配器或直接调用 Keycloak 的 API,将应用与 Keycloak 集成。
配置应用的认证和授权策略。
4. 自定义和扩展:
根据需求进行自定义,如定制登录界面、邮件模板,或扩展 Keycloak 的功能。
01 安装 Keycloak
Pre Start
1、需要准备本地的云容器环境,可以参考本文最后的往期文章推荐。在之前的文章中我采用的 Rancher Desktop 来构建本地的云环境,是因为 Docker Desktop 开始商用收费,如果是个人电脑做研究,还是可以直接使用 Docker Desktop 的。两者总体体验下来差异不大。
2、官方给出的 quick start demo 实在是太简单了,没有高可用,没有数据存储,我会调整脚本以支持高可用 HA、MySQL 数据库存储防止数据丢失等,最终以 K8S yaml 脚本进行部署。
3、Keycloak的数据默认持久化在本地文件中,在生产环境中需要通过数据库来持久化数据,我这里使用容器化部署的 MySQL。
4、通过 Ingress 配置域名,并且自动配置 HTTPS,可以参考往期文章,这里因为是本地安装,暂时改用 NodePort 方式访问。
Docker
本地快速启动体验可以使用这种方式。
docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:25.0.1 start-dev
安装成功后通过本地 8080 端口进行访问,用户名和密码分别为 admin、admin。
Kubernetes
脚本编写:
支持高可用 HA K8S,同时数据持久化到 MySQL 中。其中k8s secret 中的配置按需进行调整,完整的脚本如下:
apiVersion: v1
kind: Service
metadata:
name: keycloak
labels:
app: keycloak
spec:
ports:
- name: http
port: 8080
targetPort: 8080
nodePort: 30008
selector:
app: keycloak
type: NodePort
---
apiVersion: v1
kind: Secret
metadata:
name: keycloak-secret
labels:
app: keycloak
type: Opaque
data: # changme
keycloak_admin_user: YWRtaW4= # admin
keycloak_admin_password: YWRtaW4= # admin
db_provider: bXlzcWw= # mysql
db_url: amRiYzpteXNxbDovL215c3FsLmRldm9wcy5zdmMuY2x1c3Rlci5sb2NhbDozMzA2L2tleWNsb2FrP2NoYXJhY3RlckVuY29kaW5nPVVURi04 # jdbc:mysql://mysql.devops.svc.cluster.local:3306/keycloak?characterEncoding=UTF-8
db_user: cm9vdA== # root
db_password: cGFzc3dvcmQ= # password
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
labels:
app: keycloak
spec:
replicas: 2 # changeme
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
containers:
- name: keycloak
image: quay.io/keycloak/keycloak:25.0.1
args:
- --verbose
- start
env:
- name: KEYCLOAK_ADMIN
valueFrom:
secretKeyRef:
name: keycloak-secret
key: keycloak_admin_user
- name: KEYCLOAK_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-secret
key: keycloak_admin_password
- name: KC_PROXY
value: "edge"
- name: KC_DB
valueFrom:
secretKeyRef:
name: keycloak-secret
key: db_provider
- name: KC_DB_URL
valueFrom:
secretKeyRef:
name: keycloak-secret
key: db_url
- name: KC_DB_USERNAME
valueFrom:
secretKeyRef:
name: keycloak-secret
key: db_user
- name: KC_DB_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-secret
key: db_password
ports:
- name: http
containerPort: 8080
readinessProbe:
httpGet:
path: /realms/master
port: 8080
resources:
limits:
cpu: "4"
memory: 2048M
requests:
cpu: "2"
memory: 1024M
核心的环境变量如下表所示:
部署:
$ kubectl apply -f keycloak.yaml -n dev
$ kgp -n dev
NAME READY STATUS RESTARTS AGE
keycloak-f6cff9b6c-lc6bj 1/1 Running 0 115s
keycloak-f6cff9b6c-nbm2w 1/1 Running 0 115s
登录验证:
使用上面的指定端口 30008 进行访问
登录 MySQL 控制台可以看到初始化了很多表和基本数据:
登录成功后页面:
02 配置 Keycloak
1)创建一个名为eric-realm
的新Realm,Realm 在 Keycloak 中代表租户:
2)创建用户 eric
:
3)设置用户密码:
Eric用户首次登录,因 Temporary 开启了,首次登录需要修改密码:
登录Keycloak的地址为
https://${Keycloak 服务域名}/realms/${用户所在realm}/account
。
4)Client端配置:
Client为请求Keycloak对用户进行身份验证的客户端。用户设置完成后,需完成Client的设置。
设置Valid redirect URIs为http://* ,用于设置浏览器登录成功后有效的重定向URL,本例的http://* 表示匹配所有HTTP重定向的网址。然后单击Save。
5)添加 Client Scope:在多个客户端之间共享的一组通用协议Mapper和Role。
添加 client scope 到 client:
6)验证 Client 登录
以下命令需要替换成你的 keycloak 域名、realm 名称、用户名、用户密码、以及 client secret。
curl -ks -X POST http://localhost:30008/realms/eric-realm/protocol/openid-connect/token \
-d grant_type=password -d client_id=eric-client \
-d username=eric -d password=password -d scope=openid \
-d client_secret=WYK8RN2k5VQoARy87LnXpPi27h0cPCSP
成功后,可以获取到 access_token:
7)token 过期时长配置:
03 集成 Keycloak 和 自定义扩展
配置好 keycloak 后,我们还要进行如下集成工作:
1、前端界面自定义一个符合企业的 SSO 界面,使用用户名和密码登录,请求上面keycloak提供的获取 access_token 接口
2、批量用户导入:之前看到如果一个新建,数据量大的话,简直。。。初步看有两种解决思路,通过 admin console 获取到 create user 的 api 接口和参数,通过代码批量进行创建;还有一种方式通过 MySQL 数据库直接导入;以上两种方式没有亲自做过,但是落地可行性应该没问题。
3、Two-factor 的支持:目前官网来看,只支持Google Authenticator 和 FreeOTP,需要额外进行定制。我简单查询了下,网络上已经有解决方案了,但是我本地没有发送短信、邮件的服务器,暂时无法验证,感兴趣的可以看下:https://github.com/dasniko/keycloak-2fa-sms-authenticator
4、多种密码策略的支持
总结
使用 Keycloak 可以快速搭建起来一个 SSO 单点登录系统,同时基于 MySQL 进行数据的持久化,就算 k8s deployment 应用 crash 或者 强制将应用删除等,当我们重新启动应用时,数据还是可以通过 DB 进行加载,不会出现数据丢失的情况,同时使用 K8S 云原生技术,可以根据企业的用户体量和未来持续增长的数据量,进行动态的副本扩容,真正做到企业级高可用的 SSO 系统。
千万要切记,官方的 demo 不用用于生产环境!!!用于个人学习即可。
Keycloak 有非常多的有点,但个人认为会有如下几点不足:
1、终究使用的现有的工具,不一定满足企业对于安全的所有诉求,而且企业对于安全相关一般都是集团统一制定,需要严格遵守,这点非常重要。
2、扩展定制化有一定的技术门槛,需要阅读该平台的开源代码,从而进行代码扩展,然后打包成 Docker 镜像的等