写在最前

Keycloak 是一个开源的 身份和访问管理(IAM) 解决方案,由 Red Hat 维护。它提供 单点登录(SSO)OAuth 2.0OpenID Connect(OIDC)SAML 2.0 支持,帮助开发者轻松集成身份认证和授权功能到应用程序中。

核心功能:

用户管理:集中管理用户、角色和权限
多协议支持:OIDC、OAuth 2.0、SAML
社交登录:集成 Google、GitHub、微信等第三方登录
LDAP/AD 集成:与企业目录服务对接
多租户支持:适用于 SaaS 应用

适用场景:

  • 企业级应用:统一身份认证(SSO)

  • 微服务架构:集中式授权(JWT/OAuth2)

  • 云原生应用:Kubernetes、Docker 友好

1. 前置要求

在部署 Keycloak 前,我已经选用 PostgreSQL 作为数据库,需要提前执行 SQL,创建好 keycloak 数据库和对应的账号密码,以便 Keycloak 能正常连接使用。

-- 1. 创建 Keycloak 专用用户(Role)
CREATE ROLE keycloak WITH 
  LOGIN 
  NOSUPERUSER 
  NOCREATEDB 
  NOCREATEROLE 
  INHERIT 
  NOREPLICATION 
  CONNECTION LIMIT -1 
  PASSWORD '123456';

-- 2. 创建 Keycloak 数据库并设置所有者
CREATE DATABASE keycloakdb
    WITH 
    OWNER = keycloak
    ENCODING = 'UTF8'
    LC_COLLATE = 'en_US.utf8'
    LC_CTYPE = 'en_US.utf8'
    TABLESPACE = pg_default
    CONNECTION LIMIT = -1;

-- 3. 切换到 keycloakdb 数据库后执行的语句
-- 注意:在 Navicat 中你需要手动选择 keycloakdb 数据库后再执行以下语句

-- 4. 授予用户对 public schema 的权限
GRANT ALL ON SCHEMA public TO keycloak;

-- 5. 授予用户对所有表的权限
ALTER DEFAULT PRIVILEGES 
    FOR ROLE keycloak
    IN SCHEMA public
    GRANT ALL ON TABLES TO keycloak;

-- 6. 授予用户对序列的权限
ALTER DEFAULT PRIVILEGES 
    FOR ROLE keycloak
    IN SCHEMA public
    GRANT ALL ON SEQUENCES TO keycloak;

-- 7. 授予用户对函数的权限
ALTER DEFAULT PRIVILEGES 
    FOR ROLE keycloak
    IN SCHEMA public
    GRANT ALL ON FUNCTIONS TO keycloak;

-- 8. 授予用户对类型的权限
ALTER DEFAULT PRIVILEGES 
    FOR ROLE keycloak
    IN SCHEMA public
    GRANT ALL ON TYPES TO keycloak;

2. docker 部署

3. kubernetes 部署

3.1 deployment

kind: Deployment
apiVersion: apps/v1
metadata:
  name: keycloak
  namespace: default
  annotations:
    deployment.kubernetes.io/revision: '5'
    kubesphere.io/creator: admin
spec:
  replicas: 1
  selector:
    matchLabels:
      app: keycloak
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: keycloak
      annotations:
        kubesphere.io/creator: admin
        kubesphere.io/imagepullsecrets: '{}'
    spec:
      containers:
        - name: keycloak
          image: 'quay.io/keycloak/keycloak:24.0.4'
          args:
            - start-dev
          ports:
            - name: http-0
              containerPort: 8080
              protocol: TCP
          env:
            - name: KC_DB
              value: postgres
            - name: KC_DB_URL
              value: 'jdbc:postgresql://postgres.default:5432/keycloakdb'
            - name: KC_DB_USERNAME
              value: keycloak
            - name: KC_DB_PASSWORD
              value: '123456'
            - name: KEYCLOAK_ADMIN
              value: admin
            - name: KEYCLOAK_ADMIN_PASSWORD
              value: admin
          resources: {}
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
          imagePullPolicy: IfNotPresent
      restartPolicy: Always
      terminationGracePeriodSeconds: 30
      dnsPolicy: ClusterFirst
      securityContext: {}
      schedulerName: default-scheduler
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 25%
      maxSurge: 25%
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 600

3.2 service

方便调试我就是用NodePort暴露端口

kind: Service
apiVersion: v1
metadata:
  name: keycloak
  namespace: default
  labels:
    app: keycloak
  annotations:
    kubesphere.io/creator: admin
spec:
  ports:
    - name: http-8080
      protocol: TCP
      port: 8080
      targetPort: 8080
      nodePort: 31364
  selector:
    app: keycloak
  clusterIP: 10.233.17.170
  clusterIPs:
    - 10.233.17.170
  type: NodePort
  sessionAffinity: None
  externalTrafficPolicy: Cluster
  ipFamilies:
    - IPv4
  ipFamilyPolicy: SingleStack
  internalTrafficPolicy: Cluster

4. 使用方式

4.1 配置汉化

image-ONRl.png

4.2 创建领域

在 Keycloak 中,领域(Realm)是对公司、组织或单位的抽象,用于实现多租户或业务隔离,是系统中的顶层结构。在这里我就使用我的名字作为领域。

4.3 创建客户端

在 Keycloak 中,客户端(Client) 代表的是一个领域(Realm)下的应用系统或服务。可以是 Web 应用、移动端应用,也可以是后端服务。例如,在名为 "tanqidi" 的领域下,test-androidtest-service 都可以作为客户端注册到 Keycloak 中,分别代表 Android 应用和后端服务。在此我就先拿test-service这个java后端作为客户端演示创建出来。

4.3.1 通用设置

4.3.2 功能配置

由于 test-service 是一个后端服务,它并不是一个对外公开访问的客户端项目,因此必须启用身份认证功能。所有对其接口的访问都需要经过 Keycloak 的授权登录,确保只有经过授权的用户或服务才能访问受保护的资源。

4.3.3 登录配置

有效的重定向 URI(Valid Redirect URI) 是用来指定用户在通过 Keycloak 完成登录后,应该被重定向回哪个地址。在我的例子中,访问 test-service 的接口时,如果用户未登录,系统会自动跳转到 Keycloak 的登录界面。用户登录成功后,Keycloak 会将用户重定向回最初发起请求的客户端地址。

目前我在开发调试阶段,因此将重定向 URI 设置为 http://localhost:8080/*,这样登录完成后可以正确跳回本地运行的 Java 应用。待系统上线后,这个地址会替换为生产环境的域名,例如:https://keycloak.tanqidi.com/*,确保身份验证流程在生产环境中也能正常工作。

4.4 客户端角色

进入 Keycloak 管理后台,点击左侧菜单中的“客户端”,选择之前创建的客户端 test-service,然后切换到“角色”标签页。在这个页面中,我们为该客户端创建两个角色,分别是 test-service-admin、test-service-user,通过将这些角色分配给不同的用户,可以实现基于角色的访问控制策略,确保用户根据其角色身份访问相应的资源。

image-gSvm.png

image-aCtH.png

image-YDDz.png

4.5 用户管理

我创建了两个用户账号,分别是 test1 和 test2。创建完成后,需要点击每个用户进入其详情页面,切换到“凭证”页面,为用户设置一个登录密码。在设置密码时,需要注意关闭“临时密码”选项,否则用户在首次登录时会被强制要求修改密码。关闭该选项后,设置的密码将作为用户的正式登录密码,无需修改即可直接使用。这样可以方便我们后续进行登录测试或权限验证。

image-eIrK.png

image-uPnc.png

image-QfWG.png

4.5.1 角色映射

进入用户详情页面后,点击“角色映射”选项卡,然后点击“分配角色”按钮。在弹出的界面中,先点击“按客户端筛选”,再输入 test 进行搜索,就可以看到我们之前为客户端 test-service 创建的两个角色:test-service-admin 和 test-service-user。

接下来,为用户 test1 分配 test-service-admin 角色,表示该用户具有管理员权限;为用户 test2 分配 test-service-user 角色,表示该用户为普通使用者。通过这种方式,我们完成了用户与角色的绑定,实现了基于角色的权限管理。

image-JFMR.png

4.6 客户端范围

在实现基于角色的权限控制时,有一个非常关键但容易忽略的配置。如果不加处理,用户登录成功后返回的 ID Token 默认是不会包含角色信息的,尤其是 realm_access 中的角色字段。这样一来,后端在获取用户信息时就无法判断其所拥有的角色,也就无法进行基于角色的访问控制。

让我们进入客户端的作用域(Client Scopes),找到 roles,然后点击进入其映射(Mappers)配置。在映射列表中找到并点击 realm roles,将“添加到访问令牌(Add to access token)”和“添加到用户信息(Add to userinfo)”这两个选项都启用。完成这些配置后,Keycloak 就会在用户登录成功时,把其所属的角色信息一并包含在访问令牌和用户信息中返回给后端服务。这样后端就可以正确识别用户的角色,从而实现基于角色的权限控制。

image-loII.png

image-KYZa.png

image-RhtJ.png

4.7 访问令牌寿命

客户端 > [xxx领域] > 高级 > 访问令牌寿命,通过该配置可以控制登录后的Token过期时间。

4.8 Ingress 配置

kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
  name: keycloak
  namespace: default
  annotations:
    kubernetes.io/ingress.class: nginx
    kubesphere.io/creator: admin
spec:
  tls:
    - hosts:
        - keycloak.tanqidi.com
      secretName: keycloak-tanqidi-ssl
  rules:
    - host: keycloak.tanqidi.com
      http:
        paths:
          - path: /
            pathType: ImplementationSpecific
            backend:
              service:
                name: keycloak
                port:
                  number: 8080

但是这样会出现一个错误,明明是https但是它响应的却是http导致被浏览器拦截,我们需要重新修改一下deployment。

--hostname 设置成你暴露的域名;
--hostname-strict=false 可以避免因为解析不一致出错;
--proxy=edge 让 Keycloak 信任来自 Nginx ingress 的 X-Forwarded-Proto: https

spec:
  containers:
    - name: keycloak
      image: 'quay.io/keycloak/keycloak:24.0.4'
      args:
        - start
        - --proxy=edge
        - --hostname=keycloak.tanqidi.com
        - --hostname-strict=false
        - --http-enabled=true

# The following used options or option values are DEPRECATED and will be removed in a future release:
- proxy: Use proxy-headers.
说明 ✅ --proxy=edge 参数 已经生效了,只是提示你这个参数将在未来版本中被弃用。

✅ 正确做法(Keycloak 24+ 推荐)Keycloak 24 及以上版本建议使用新的参数:
args:
  - start
  - --proxy-headers=xforwarded
  - --hostname=keycloak.tanqidi.com
  - --hostname-strict=false

# 如果你的args中写的是start那么他会以为你是生产会强制要求挂在https进去,但是我们是通过ingress进来的,没必要这样做。所以还需要加入一句 --http-enabled=true 这样他就不会强制要求https证书了。相反如果你写的是start-dev 那么连这句http-enabled=true都省了。

4.x 配置总结

我们主要在 Keycloak 中完成了以下配置:创建了一个名为 tanqidi 的领域,并在该领域下创建了一个名为 test-service 的后端应用客户端。随后,我们又创建了两个用户 test1 和 test2,并为他们分配了不同的客户端角色,test1 对应的是 test-service-admin,而 test2 对应的是 test-service-user。至此,Keycloak 的核心工作已经完成,它负责了用户身份认证、角色的创建和分配。

但接下来会有一个疑问:这些角色和权限是如何与后端 Java 应用中的访问路径关联起来的?例如,/apis/user/* 路径应该只能被具有 user 权限的用户访问,而 /apis/admin/* 路径应该只能由 admin 权限的用户访问。为什么这些访问控制并没有直接在 Keycloak 中配置?

这是因为 Keycloak 本身只负责 认证(Authentication)角色权限的集中管理(Authorization Metadata),但并不会直接控制你应用中的具体业务访问逻辑。也就是说,Keycloak 只是帮你确认了“这个人是谁”和“他拥有什么角色”,但“这个角色能访问哪些接口”这件事,需要你在自己的后端代码中去实现。这种方式的好处在于更灵活、更贴合实际业务。如果你在 Keycloak 里配置接口权限,一旦后端代码新增或修改接口,你就不得不在 Keycloak 中同步更新权限策略,既繁琐又容易出错。

因此,最合理的做法是:Keycloak 负责认证与角色定义,后端应用读取用户的角色信息后,根据自定义的权限控制逻辑去判断是否允许访问对应的接口路径。这种方式既保持了职责的清晰,也方便开发和维护。

最后别忘了最要命的是客户端范围,必须要配置一下将realm roles 返回给用户信息与用户token中,不然你会发现无论后续怎么调试代码都会莫名奇妙的发现无法获取角色信息。

5. 对接后端

https://gitea.tanqidi.com/demo/spring-boot-3.5.0-keycloak-24

以下是一个基于 Spring Boot 3.5.0 和 Keycloak 24 的连接配置示例,帮助读者顺利完成相关配置与集成流程。只需根据示例中的 OAuth2LoginSecurityConfig 类和 application.properties 文件,适当调整参数即可。

在示例中,用户 test1 拥有管理员(admin)权限,可访问所有接口;而用户 test2 仅具备普通用户(user)权限。当 test2 访问 http://localhost:8080/api/admin/users 接口时,将返回以下响应,表示无访问权限:

{
  "code": 403,
  "message": "没有权限访问该资源",
  "data": null
}

5.1 设计心得

后端不推荐让 API(如 http://localhost:8080/api/admin/users)未认证时自动跳转到 Keycloak 登录页面,它主要完成权限校验功能就好了,不推荐的主要有以下原因:

  1. API 设计规范 RESTful API 设计标准是:未认证时返回 401 Unauthorized,由前端或客户端决定如何处理(如跳转登录页、弹窗等)。自动跳转会破坏 API 的通用性和一致性。

  2. 前后端分离场景 前端通常通过 Ajax/fetch/axios 调用 API。如果后端直接返回 302 跳转,前端拿到的不是标准的 JSON 错误,而是 HTML 登录页,前端无法正确处理,用户体验差。

  3. 跨域和安全问题 自动跳转到第三方登录页(如 Keycloak)时,可能会遇到 CORS 问题,导致前端无法拿到正确的响应。

  4. 客户端/移动端兼容性、第三方系统等非浏览器客户端无法处理重定向到登录页,直接 401 更通用。

  5. 可控性和灵活性返回 401 后,前端可以根据业务需求灵活处理(如弹出登录框、跳转 SSO、提示用户等),而不是被动跳转。

6. 对接前端