ToB企服应用市场:ToB评测及商务社交产业平台

标题: keycloak了解阐明 [打印本页]

作者: 南飓风    时间: 2024-12-27 13:11
标题: keycloak了解阐明
一、安装keycloak应用

接纳docker的方式运行一个keycloak应用,方便进行学习。
在我的ubuntu捏造机中,写一个docker-compose.yml文件,内容如下,安装一个keycloak、一个postgres数据库。
  1. version: "3"
  2. services:
  3.   auth:
  4.     image: jboss/keycloak
  5.     ports:
  6.     - "8080:8080"
  7.     environment:
  8.     - "KEYCLOAK_USER=admin"
  9.     - "KEYCLOAK_PASSWORD=admin"
  10.     - "DB_VENDOR=postgres"
  11.     - "DB_ADDR=postgres"
  12.     - "DB_DATABASE=postgres"
  13.     - "DB_USER=postgres"
  14.     - "DB_PASSWORD=admin"
  15.     - "PROXY_ADDRESS_FORWARDING=true"
  16.   postgres:
  17.     image: postgres:9.6
  18.     ports:
  19.     - "5432:5432"
  20.     environment:
  21.     - "POSTGRES_PASSWORD=admin"
复制代码
执行如下下令,启动容器
  1. docker-compose -f docker-compose.yml up -d
复制代码
启动成功后,我们通过vscode编辑器的docker-compose插件,能看到捏造机中运行了容器

我们用8080端口访问,http://192.168.71.133:8080/, 并使用账户admin、admin登录即可。
二、简朴使用示例

创建Realm
创建一个新的realm: demo,后续所有的客户端、用户、脚色等都在此realm中创建。
创建客户端
创建后端应用客户端:golang-demo,  Access Type 选择 bearer-only。 生存之后,会出现 Credentials的 Tab,记录下这里的secret,后面要用到。
关于客户端的访问类型(Access Type)
keycloak的访问类型共有3种:

创建脚色
创建2个脚色:ROLE_ADMIN、ROLE_CUSTOMER
创建用户
创建2个用户:admin、customer
绑定用户和脚色
给admin用户分配脚色 ROLE_ADMIN
给customer用户分配脚色ROLE_CUSTOMER

三、设置阐明

Consent Required

设置client的时间,有这个选项,可以on、off。
在Keycloak中,“Consent Required”通常与用户授权和同意流程相关,这是一个重要的安全特性,用于确保用户明确知道并同意应用程序或服务将访问其哪些数据或执行哪些操纵。这种机制在符合GDPR(通用数据保护条例)等隐私法规的情况中尤为重要。
Keycloak作为一个开源的身份和访问管理(IAM)解决方案,支持多种认证和授权协议,包括OAuth 2.0和OpenID Connect。在这些协议中,“Consent Required”流程通常与OAuth 2.0的授权过程或OpenID Connect的身份验证和授权过程相结合。
“Consent Required”流程概述

在Keycloak中设置“Consent Required”

Keycloak提供了机动的设置选项,允许管理员根据需要启用或禁用“Consent Required”流程。以下是一些根本的设置步调(请注意,详细步调可能因Keycloak版本和摆设方式而异):
注意事项


Keycloak的官方文档和社区论坛是获取有关“Consent Required”流程和其他相关设置的最新和详细信息的最佳泉源。如果您在使用Keycloak时遇到任何题目,发起查阅官方文档或寻求社区的资助。

使用场景枚举

1、隐式授权过程

用户访问单页面Web应用(SPA)时,通常会遵循OAuth 2.0或OpenID Connect(OIDC)的授权流程。这些流程涉及重定向用户到Keycloak的登录页面以进行身份验证和授权。
这里的详细流程,参考我写的OAuth-CSDN博客, 里面有隐式授权模式的流程介绍。
Keyccloak这个软件安装后,我们创建一个client, client的 Client Protocol(终端协议)选 openid-connect。 访问类型选 public或者confidential都行,这时就有个 Implicit Flow Enabled(隐式流程启用)的开关选项。它允许客户端通过重定向URI直接获取访问令牌(access token) 和/或身份令牌(ID token),而无需与keycloak的令牌端点进行额外的哀求。
隐式流程重要用于客户端无法安全地存储秘密(如客户端密钥)的情况,如某些JavaScript客户端运行在浏览器中。
流程:

用keycloak实现隐式授权的流程如下:
详细过程模拟:

1、我们在keycloak中创建

2、用户授权时的登录地址
授权URL的详细格式如下:
  1. http://<keycloak-server-ip>:<port>/auth/realms/<realm-name>/protocol/openid-connect/auth?  
  2. response_type=token&  
  3. client_id=<client-id>&  
  4. redirect_uri=<redirect-uri>&  
  5. state=<state>&  
  6. nonce=<nonce>&  
  7. scope=openid%20profile%20email
复制代码
根据我们的keycloak设置,得到如下地址 
  1. http://192.168.71.133:8080/auth/realms/demo/protocol/openid-connect/auth?  
  2. response_type=token&  
  3. client_id=golang-demo&  
  4. redirect_uri=https%3a%2f%2f127.0.0.1%3a8888%2fauthorizaiton&  
  5. state=22222&  
  6. nonce=1111&  
  7. scope=openid%20profile%20email
复制代码
3、用户访问需要权限的页面
假设用户通过浏览器访问web服务,访问到需要权限的页面,vue的代码检测到没有合法的jwt的token,就把用户重定向到keycloak的登录页面。地址就是上一步的地址。
我们可以直接用浏览器模拟访问这个地址,会得到一个html的登录页面。

4、用户授权
基于上面页面,输入用户名密码登录,(比如songtaiwu,admin123),会触发表单提交,地址如下
  1. http://192.168.71.133:8080/auth/realms/demo/login-actions/authenticate?session_code=AftDjyPEtzFzFZZ_vhZOSBxwqyaA_fHOZBm9LW_ZIag&execution=458ee02a-f125-4700-9bd4-5db9d6b3bb77&client_id=golang-demo&tab_id=4YlLZW39T_4
  2. ​POST请求的载荷是:
  3. username=songtaiwu&password=admin123&credentialId=
复制代码
kecloak那边会根据用户名密码校验,校验失败就返回失败HTML,让用户再次数据。
如果成功,会相应 302 重定向,Location内容如下

http://127.0.0.1:8888/authorizaiton#state=22222&session_state=b61f31a0-e669-4662-b379-9ac6e91b76f4&access_token=eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ0NXdlc2twTU01dUtWdmtxN3B5MGdtODJISzQ1ek54dDd2UTBkLTFHS1l3In0.eyJqdGkiOiJhZDRkZDdlYy05ZWU3LTQyODUtODM2OC1hYWE5OTYyYWI3NmQiLCJleHAiOjE3MjE2MzU1OTIsIm5iZiI6MCwiaWF0IjoxNzIxNjM0NjkyLCJpc3MiOiJodHRwOi8vMTkyLjE2OC43MS4xMzM6ODA4MC9hdXRoL3JlYWxtcy9kZW1vIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjU0NmFmZTQxLWU5NGQtNGY4NS04NGVjLTQxZDk1Y2JlODk3ZCIsInR5cCI6IkJlYXJlciIsImF6cCI6ImdvbGFuZy1kZW1vIiwibm9uY2UiOiIxMTExIiwiYXV0aF90aW1lIjoxNzIxNjM0NjkyLCJzZXNzaW9uX3N0YXRlIjoiYjYxZjMxYTAtZTY2OS00NjYyLWIzNzktOWFjNmU5MWI3NmY0IiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoic29uZ3RhaXd1In0.blSXtloHi1Q-PiBmcB3uFsY0bRhu5pk1EdKBGF9W5ePS74AXL3bDpfhipeieFJSyWXAajVr-vFe-JQvisei6jKoZikjSt8XK35OZwc1qhvKUBLUtx9CDnsiz-Bp77bskKLjtfSeVsum2r5ZIVgt-FLz6_bYFbLzEcmLSAWvFmnwDJezCLNgASMwP8s9DmqEu9dm1qIzbBVdVqyAKRNBfqFKWai0CgcJULrkd9KM1J6vl14et9BIbrseJidUrjazbKACmF0MUQL0_RU9_NAxBZt8D3uHipQSu7rFYPwcUAJ89-OO9WKcY88RIYx786Ve78DtOtkFrp_F7VkBzjBQ2TA&token_type=bearer&expires_in=900
可以看到,keycloak通过重定向的方式已经把token给到我们了。
5、使用token
我们假设这时间我们单网页项目已经获取到用户的token了,并把token存到浏览器本地,后续访问都会带上token。
vue端也可以调用keycloak的接口,来验证这个token
  1. GET http://192.168.71.133:8080/auth/realms/demo/protocol/openid-connect/userinfo
复制代码
在header中的 Authorization带上上一步归去的token,格式 Bearer  xxxxxxx
keycloak校验token正常,会返回用户信息,比如
  1. {
  2.         "sub": "546afe41-e94d-4f85-84ec-41d95cbe897d",
  3.         "email_verified": false,
  4.         "preferred_username": "songtaiwu"
  5. }
复制代码
可以看到用户id和我们在keycloak后台看到的一样

总结:

如果keycloak上面设置的客户端client想要用于“隐式授权”, 其访问类型设置成public 或者 confidential没区别,由于隐式授权的过程,都不会要client-secret。设置public的话客户端是没有client seret,设置成confidential那么客户端有client-secret,但是隐式流程又不需要。
隐式流程在现代应用中已不太推荐。

2、授权码模式过程

Keycloak的授权码流程是OAuth 2.0协议中的一个详细实现,重要用于在客户端(如Web应用、移动应用等)和认证服务器(Keycloak)之间安全地交换认证和授权信息。以下是Keycloak授权码流程的重要步调,基于OAuth 2.0协议的尺度流程:
流程:

1. 客户端发起授权哀求

2. 用户授权

3. Keycloak返回授权码

4. 客户端使用授权码哀求访问令牌

5. Keycloak返回访问令牌

6. 客户端使用访问令牌访问受保护的资源

注意事项

详细过程模拟:

1、我们在keycloak中创建

2、用户授权要访问的地址
授权URL的详细格式如下,和隐式授权流程的差异就是这里的response_type=code
  1. http://<keycloak-server-ip>:<port>/auth/realms/<realm-name>/protocol/openid-connect/auth?  
  2. response_type=code&  
  3. client_id=<client-id>&  
  4. redirect_uri=<redirect-uri>&  
  5. state=<state>&  
  6. nonce=<nonce>&  
  7. scope=openid%20profile%20email
复制代码
根据我们的keycloak设置,得到如下地址 
  1. http://192.168.71.133:8080/auth/realms/demo/protocol/openid-connect/auth?  
  2. response_type=code&  
  3. client_id=golang-demo&  
  4. redirect_uri=https%3a%2f%2f127.0.0.1%3a8888%2fauthorizaiton&  
  5. state=22222&  
  6. nonce=1111&  
  7. scope=openid%20profile%20email
复制代码
3、用户访问需要权限的页面
当用户代理(浏览器)访问受到权限保护的资源时,浏览器发奉上面的url哀求到keycloak服务器。
keycloak检验url中的参数后,如果检验通过,会返回给浏览器keycloak的登录页面,让用户登录并授权。

4、用户授权
基于上面页面,输入用户名密码登录,(比如songtaiwu,admin123),会触发表单提交,地址如下
  1. http://192.168.71.133:8080/auth/realms/demo/login-actions/authenticate?session_code=zBEvPnlhxCp_sLxS4KBxI9eojKuOqVnjapXMBESombc&execution=458ee02a-f125-4700-9bd4-5db9d6b3bb77&client_id=golang-demo&tab_id=EZtdwT-D684
  2. ​POST请求的载荷是:
  3. username=songtaiwu&password=admin123&credentialId=
复制代码
kecloak那边会根据用户名密码校验,校验失败就返回失败HTML,让用户再次数据。
如果成功,会相应 302 重定向,Location内容如下
会把哀求重定向到redirect_uri指定地址,并且在其后附上了code值。
http://127.0.0.1:8888/authorizaiton?state=22222&session_state=8254124f-2a5e-4553-bffc-c1d59774c927&code=6bc77314-9587-4032-a606-0f51dfcd51b3.8254124f-2a5e-4553-bffc-c1d59774c927.fd9fcdf7-fd11-4646-ab03-19352dde948d
可以看到,keycloak通过重定向的方式已经把code给到我们了。
5、服务端获取到code
上面步调的回调地址,一样平常就是服务端应用的一个api接口。即让用户授权之后,keycloak通过重定向,让浏览器继承访问服务端api,这样就把code给到了服务器。
这里我们不模拟这个接口调用,就假定我们的web服务端(后端)拿到了code。
6、服务端获取到token

当服务端(即客户端的后端服务)从Keycloak的授权流程中获取到授权码(code)后,它会调用Keycloak的令牌端点(token endpoint)来获取访问令牌(access token)和其他可能的令牌(如革新令牌refresh token)。Keycloak遵循OpenID Connect和OAuth 2.0协议,因此令牌端点的URL格式通常是尺度化的。
令牌端点的URL通常遵循以下格式:
  1. POST https://{keycloak-server}/auth/realms/{realm-name}/protocol/openid-connect/token
复制代码

在调用令牌端点时,服务端需要以POST哀求的方式发送以下参数(通常包含在哀求体中,且内容类型为application/x-www-form-urlencoded):


据上面,我们现实哀求如下,用APIPOST工具发起的哀求
  1. curl --location --request POST --X POST 'http://192.168.71.133:8080/auth/realms/demo/protocol/openid-connect/token' \
  2. --header 'User-Agent: Apipost client Runtime/+https://www.apipost.cn/' \
  3. --header 'Content-Type: application/x-www-form-urlencoded' \
  4. --data 'grant_type=authorization_code' \
  5. --data 'client_id=golang-demo' \
  6. --data 'client_secret=5a674d46-378e-42d2-be61-68cdb72aae99' \
  7. --data 'redirect_uri=https://127.0.0.1:8888/authorizaiton' \
  8. --data 'code=e88504ef-b383-47d9-80b1-1957dcca9c07.81708314-8eb1-4874-8224-5e7c7814c671.fd9fcdf7-fd11-4646-ab03-19352dde948d'
复制代码
返回值
  1. {
  2.         "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ0NXdlc2twTU01dUtWdmtxN3B5MGdtODJISzQ1ek54dDd2UTBkLTFHS1l3In0.eyJqdGkiOiIyNGNkMmJmZi1jNDA1LTQxNzMtODE5OS1lMzU5MjBjN2RlOTgiLCJleHAiOjE3MjE3MDE1NDksIm5iZiI6MCwiaWF0IjoxNzIxNzAxMjQ5LCJpc3MiOiJodHRwOi8vMTkyLjE2OC43MS4xMzM6ODA4MC9hdXRoL3JlYWxtcy9kZW1vIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjU0NmFmZTQxLWU5NGQtNGY4NS04NGVjLTQxZDk1Y2JlODk3ZCIsInR5cCI6IkJlYXJlciIsImF6cCI6ImdvbGFuZy1kZW1vIiwibm9uY2UiOiIxMTExIiwiYXV0aF90aW1lIjoxNzIxNjk5NzI3LCJzZXNzaW9uX3N0YXRlIjoiODE3MDgzMTQtOGViMS00ODc0LTgyMjQtNWU3Yzc4MTRjNjcxIiwiYWNyIjoiMCIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoic29uZ3RhaXd1In0.udvH85bGySpz_uAjlgu0-Yeo0qAnHJuiO1VMq2pB8lM31fCAfrRgLQsVbBFMXEwD-IDlfLekuU5JugS_ChIL7uGMz8ubUj6cPsAFq3DPVsThpFhoEiM5FofFbF7VzUeNNiSwuD1e6ZIGexqAPXHS0EyGwb00d173ViPoMTKl9wOApZoD82kWvf7vGGvIyTB1FZXAPO5RPSUEK-84fe7Hw2kAmUCBaQm0uqdMHUpnWM4n_iBPZEV61S2Uxwmg950UhVSayI6vP20LXgKTp4EAN1YTVv1vYzUEdzuVEif2Jq-DIUxqhHpsEg1JR9UnvbycKhdXUSAUazFaD0Ixk9WB0A",
  3.         "expires_in": 300,
  4.         "refresh_expires_in": 1800,
  5.         "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJiMjJmOTEwZi0wOWVhLTQ4NTQtODBlYS1lN2Q3ZWY5ODVhZTQifQ.eyJqdGkiOiI0NjhlZTZjYy1mMWY4LTQ2YTItOWE1NC1iM2IzMDM4ZDA2MDUiLCJleHAiOjE3MjE3MDMwNDksIm5iZiI6MCwiaWF0IjoxNzIxNzAxMjQ5LCJpc3MiOiJodHRwOi8vMTkyLjE2OC43MS4xMzM6ODA4MC9hdXRoL3JlYWxtcy9kZW1vIiwiYXVkIjoiaHR0cDovLzE5Mi4xNjguNzEuMTMzOjgwODAvYXV0aC9yZWFsbXMvZGVtbyIsInN1YiI6IjU0NmFmZTQxLWU5NGQtNGY4NS04NGVjLTQxZDk1Y2JlODk3ZCIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJnb2xhbmctZGVtbyIsIm5vbmNlIjoiMTExMSIsImF1dGhfdGltZSI6MCwic2Vzc2lvbl9zdGF0ZSI6IjgxNzA4MzE0LThlYjEtNDg3NC04MjI0LTVlN2M3ODE0YzY3MSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSJ9.FToMe2GhVQL8S-tk6gnfmZ6-Advqarj3-LLcG4l0dXk",
  6.         "token_type": "bearer",
  7.         "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ0NXdlc2twTU01dUtWdmtxN3B5MGdtODJISzQ1ek54dDd2UTBkLTFHS1l3In0.eyJqdGkiOiIzNDA3ZWQ4NS1jYzlmLTQzMjMtYmU3Ny05NDg2MTZlYjA5NjAiLCJleHAiOjE3MjE3MDE1NDksIm5iZiI6MCwiaWF0IjoxNzIxNzAxMjQ5LCJpc3MiOiJodHRwOi8vMTkyLjE2OC43MS4xMzM6ODA4MC9hdXRoL3JlYWxtcy9kZW1vIiwiYXVkIjoiZ29sYW5nLWRlbW8iLCJzdWIiOiI1NDZhZmU0MS1lOTRkLTRmODUtODRlYy00MWQ5NWNiZTg5N2QiLCJ0eXAiOiJJRCIsImF6cCI6ImdvbGFuZy1kZW1vIiwibm9uY2UiOiIxMTExIiwiYXV0aF90aW1lIjoxNzIxNjk5NzI3LCJzZXNzaW9uX3N0YXRlIjoiODE3MDgzMTQtOGViMS00ODc0LTgyMjQtNWU3Yzc4MTRjNjcxIiwiYWNyIjoiMCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoic29uZ3RhaXd1In0.HURNnjbSnVWtYTSF2TRtZhh9DHYOaJDC6qm8ZOsfo2tD44OADy3g2HhaEhq2LEMROE15IWkqCQlSYfBk8-ID-dlupghnXggsJCaB7uY-nmNZf49gpr5YrgQ20_OOgrruPvipC0TZxm0F0_m7232JU7i1mEN6GSzhzZB3k-Zr4bdgKcQj57SBVM73enTsaLhllgWvUa5jZbi32DdoIQA-hz7yt6-Nvx0NqBanR7VjppI71rKO_ueJozQYhtnsEgI9kXSxgqL6TEb9YTIPGF-LyH-Hq5cvKzhda4CV69WH60n-LOfg0bzoH6yB0jqWrptBh8HwmGL9yYGYj1SwjPaPbg",
  8.         "not-before-policy": 0,
  9.         "session_state": "81708314-8eb1-4874-8224-5e7c7814c671",
  10.         "scope": "openid email profile"
  11. }
复制代码

7、服务端用token调用
我们假设这时间我们服务端已经获取到用户的token了,可以调用keycloak的接口,来验证这个token
  1. GET http://192.168.71.133:8080/auth/realms/demo/protocol/openid-connect/userinfo
复制代码
在header中的 Authorization带上上一步归去的token,格式 Bearer  xxxxxxx
keycloak校验token正常,会返回用户信息,比如
  1. {
  2.         "sub": "546afe41-e94d-4f85-84ec-41d95cbe897d",
  3.         "email_verified": false,
  4.         "preferred_username": "songtaiwu"
  5. }
复制代码
总结:

授权类型设置为public或confidential都可以。
一样平常单页面应用(SPA)设置public访问类型,由于它不具备安全存储client secret的能力。设置public,仍然可以到场OAuth的过程,这授权码模式中,只是不用client secret。
在OAuth的上下文中,访问类型(如public、confidential等)重要影响的是客户端怎样安全地存储和验证其根据。对于public类型的客户端,由于它们通常无法安全地存储客户端密钥(client secret),因此它们不应在哀求访问令牌时使用客户端密钥进行身份验证。相反,它们可能依赖于其他机制,如授权码流程加上PKCE(Proof Key for Code Exchange),来提高安全性。
需要注意的是,虽然单页面应用可能设置为public访问类型,但它们仍然可以通过OAuth流程获得必要的访问令牌,以便与受保护的资源服务器进行交互。别的,为了提高安全性,发起单页面应用遵循OAuth的最佳实践,如使用HTTPS、实施CSRF保护、限定重定向URI等。


3、前后端分离项目使用keycloak

keycloak中,创建一个新的Realm. Realm是Keycloak中用于隔离不同应用的安全域。
在Realm中,创建两个客户端,一个用于前端应用,一个用于后端服务。
设置客户端的访问类型:

设置客户端的重定向URI 和 有效URI,确保他们与前端和后端服务的现实摆设情况相匹配。


单点登录协议有哪些?CAS、OAuth、OIDC、SAML有何异同?-腾讯云开发者社区-腾讯云 (tencent.com)
OAuth 2.0 和 OpenID Connect 的根本原理和区别(干货)_oauth2.0 openid connect-CSDN博客
最详细的Keycloak教程(发起收藏):Keycloak实现手机号、验证码登陆——(三)基于springboot&keycloak+vue的前后端分离项目_keycloak 教程-CSDN博客

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4