通过 api 和 keycloak 理解OIDC认证

news2024/11/24 7:47:28

参考资料

  • 通过Keycloak API理解OAuth2与OpenID Connect

  • 什么是keycloak如何在nodejs中使用它

  • 如何通过 OIDC 协议实现单点登录?

  • https://jwt.io/#encoded-jwt

OIDC认证的简单demo

单点登录(Single Sign On)是目前比较流行的企业业务整合的解决方案之一,在多个应用系统中,用户只需要登录一就可访问所有相互信任的应用系统。而OIDC (OpenID Connect)是一个基于 OAuth 2.0 的轻量级认证 + 授权协议,是 OAuth 2.0 的超集。OIDC Provider即身份提供商,常见的微信扫码登陆场景,微信就是OIDC Provider。我们使用**node-oidc-provider**(最新版本仅支持18LTSnodejs)创建一个自己的oidc provider。

git clone git@github.com:panva/node-oidc-provider.git
cd node-oidc-provider
npm install

将我们的应用程序集成到oidc provider的方式是向其申请一个client,这里直接在配置文件中添加client

# ./example/support/configuration.js
{
  client_id: '1',
  client_secret: '1',
  grant_types: ['refresh_token', 'authorization_code'],
  redirect_uris: ['http://localhost:8080/app1.html', 'http://localhost:8080/app2.html'],
}

启动oidc provider

cd ./example
node express.js

整体的认证流程如下:

  1. 客户端呼叫登录,oidc provider检查登录状态
  2. 未登录则要求客户登录并发送authorization code到回调地址
  3. 已登陆则直接跳转到回调地址(携带authorization code
  4. authorization code转换为access_token
  5. 使用access_token获取用户信息

创建应用程序

$ mkdir app
$ vim app/app1.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>firstApp</title>
  </head>
  <body>
    <a href="http://localhost:3000/auth?client_id=1&redirect_uri=http://localhost:8080/app1.html&scope=openid profile&response_type=code&state=455356436">login</a>
  </body>
</html>
$ vim app/app2.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>secondApp</title>
  </head>
  <body>
    <a href="http://localhost:3000/auth?client_id=1&redirect_uri=http://localhost:8080/app2.html&scope=openid profile&response_type=code&state=455356436">login</a>
  </body>
</html>

启动服务器托管客户端应用

$ cd app
$ http-server .

一直报错Authorization Server policy requires PKCE to be used for this request,最后发现是默认开启了pkce

https://github.com/panva/node-oidc-provider/blob/v7.x/docs/README.md#pkcerequired

略坑,不知道这个功能是什么用,关闭之后正常

function pkceRequired(ctx, client) {
  return false;
}

访问app1的登录链接,跳转到oidc provider的授权环节,发现没有登录等待输入用户名密码(默认放行任意用户名密码)

在这里插入图片描述

确权页面,显示应用需要获取那些用户权限

在这里插入图片描述

点击continue回到最初的入口,但是url已经发生变化。下面的MDbf7u7HPwQEGFCzWkR-YAc3rnJ78ZE2Nq-oOmE_3Qnauthorization code

http://localhost:8080/app1.html?code=MDbf7u7HPwQEGFCzWkR-YAc3rnJ78ZE2Nq-oOmE_3Qn&state=455356436

通过authorization code获取access_token,之后应用程序即通过access_token访问oidc provider上的资源

curl --location --request POST 'http://localhost:3000/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=1' \
--data-urlencode 'client_secret=1' \
--data-urlencode 'redirect_uri=http://localhost:8080/app1.html' \
--data-urlencode 'code=MDbf7u7HPwQEGFCzWkR-YAc3rnJ78ZE2Nq-oOmE_3Qn' \
--data-urlencode 'grant_type=authorization_code'
{
    "access_token": "bhDGXk9yR0dJj-o_2X-YmTZqg4Lotn_UBTOmgGudPg4",
    "expires_in": 3600,
    "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InIxTGtiQm8zOTI1UmIyWkZGckt5VTNNVmV4OVQyODE3S3gwdmJpNmlfS2MifQ.eyJzdWIiOiJxd2VyIiwiYXRfaGFzaCI6IjBVbDB3b1N6dFN3azV1REdMa0xkSmciLCJhdWQiOiIxIiwiZXhwIjoxNjcyMDc3MjgyLCJpYXQiOjE2NzIwNzM2ODIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJ9.ZrxZMKxauh1qysC_jC58mz3JAPbBpzQUabreGydYBpgG9WQ65Ca_Ch85kwFpWWhLCMBl9T_cKtiRRMBtdkaWsElKlYtZ4z6p0LlmVmVOBlY2TkBh4xxg0rcLBPcyuN0ATGrMOwwhMnlCV0RRzY3lcfh5Dbd1oj_OOWlqqqXxOT1F4M9F_kCeRzLQBwCGScwDEzVLZsTEapOrZFGXpH16Jnf8C1nqs2s7WJbepE9RZwOxpjlNjTTfCi755gBWLIU5-pHBFeqNxa_CdAfQ1OYBPX9MGGdAYIwL5TPqvR3F000kgCKx7YaSLnpaczjlROQzmst4-FlbUhEip_Hd3uBFhw",
    "scope": "openid profile",
    "token_type": "Bearer"
}

通过access_token获取用户数据

curl --location --request POST 'http://localhost:3000/me' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'access_token=bhDGXk9yR0dJj-o_2X-YmTZqg4Lotn_UBTOmgGudPg4'
{
    "sub": "qwer",
    "birthdate": "1987-10-16",
    "family_name": "Doe",
    "gender": "male",
    "given_name": "John",
    "locale": "en-US",
    "middle_name": "Middle",
    "name": "John Doe",
    "nickname": "Johny",
    "picture": "http://lorempixel.com/400/200/",
    "preferred_username": "johnny",
    "profile": "https://johnswebsite.com",
    "updated_at": 1454704946,
    "website": "http://example.com",
    "zoneinfo": "Europe/Berlin"
}

OIDC核心概念和逻辑

我们通过上面的应用完成了简单的OIDC认证过程,下面的部分完整的对以上逻辑进行梳理

OIDC认证涉及的主要概念

  • Resource Owner - 用户
  • Resource Server - 服务器资源
  • Client - 用户前端程序
  • Authorization Server - 对用户认证并发送access_token

OIDC涉及的核心数据,这些关键数据可以和上面的demo相互印证

  • User Credential - 用户凭据(用户名和密码)
  • Client ID - client的唯一标识
  • Client Secret - Authroziation Server验证Client身份的标识
  • Authorization Code - 授权码,通过 User Credential + Client ID换取Authorization Code。授权码不能泄漏,且一次性有效。
  • Access Token - 访问令牌,拥有令牌者可以访问受保护的资源。通过 Authorization Code + Client ID + Client Secret 换取。Access Token在有效期内有效。
  • Refresh Token - 刷新令牌,重新获取(刷新)Access Token和Refresh Token。Refresh Token在有效期内有效。
  • ID Token - 包括会话认证的JWT,包括用户标识,identity provider,client信息

示意图如下

在这里插入图片描述

使用Keycloak完成oidc认证

创建keycloak服务的过程之前的文章已经讲过,略去不表

常用的Keycloak endpoints如下:

  • authorization_endpoint - 获取Authorization code
  • token_endpoint - 获取Access Token
  • introspection_endpointt - Token内省,可验证Token和获取Token的元信息。
  • userinfo_endpoint - 获取用户信息
  • end_session_endpoint - 用户注销
$ curl http://127.0.0.1:8090/realms/myoidc/.well-known/openid-configuration
{
    "issuer": "http://127.0.0.1:8090/realms/myoidc",
    "authorization_endpoint": "http://127.0.0.1:8090/realms/myoidc/protocol/openid-connect/auth",
    "token_endpoint": "http://127.0.0.1:8090/realms/myoidc/protocol/openid-connect/token",
    "introspection_endpoint": "http://127.0.0.1:8090/realms/myoidc/protocol/openid-connect/token/introspect",
    "userinfo_endpoint": "http://127.0.0.1:8090/realms/myoidc/protocol/openid-connect/userinfo",
    "end_session_endpoint": "http://127.0.0.1:8090/realms/myoidc/protocol/openid-connect/logout",
    "jwks_uri": "http://127.0.0.1:8090/realms/myoidc/protocol/openid-connect/certs",
}

向keycloak注册client

在这里插入图片描述

创建用户并绑定role

在这里插入图片描述

仿照demo中操作访问keycloak认证页面,输入用户名和密码跳转到回调链接(回调链接可以随便写)

curl http://127.0.0.1:8090/realms/myoidc/protocol/openid-connect/auth?client_id=testapp&redirect_uri=http://127.0.0.1:8099/&response_type=code&scope=openid

跳转到回调连接后url发生变化,获取到授权码(code后的内容)

http://127.0.0.1:8099/?session_state=dbf528e5-beea-4815-848e-7e7651216aac&code=ca78ca6c-b79c-4f09-a360-9ffd7574da6d.dbf528e5-beea-4815-848e-7e7651216aac.da5886f1-238b-4e79-8e12-0e33e601fdea

通过curl命令,使用授权码获取access_token

curl --location --request POST 'http://127.0.0.1:8090/realms/myoidc/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'code=ca78ca6c-b79c-4f09-a360-9ffd7574da6d.dbf528e5-beea-4815-848e-7e7651216aac.da5886f1-238b-4e79-8e12-0e33e601fdea' \
--data-urlencode 'client_id=testapp' \
--data-urlencode 'client_secret=V1FTVZMIUAVx52n7VM5ndrCLqwj0pGTF' \
--data-urlencode 'redirect_uri=http://127.0.0.1:8099/' \
--data-urlencode 'grant_type=authorization_code'
{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJJNEQyVGp6WUZvSWRGeTlIRGE1aFk2a2tjclRmZUNodmhLODZuN2JXTWJRIn0.eyJleHAiOjE2NzIwNzY5MjYsImlhdCI6MTY3MjA3NjYyNiwiYXV0aF90aW1lIjoxNjcyMDc1ODY5LCJqdGkiOiI2ZDI0ZGQ0My1iZGJlLTQwNWUtYmNiNC0xOWM4NTU1NGUxNWQiLCJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwOTAvcmVhbG1zL215b2lkYyIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiIzZDQ2NGM4NC1jZDEzLTQzY2ItODFiNi0yYTZlNmViOTY0ZTUiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0ZXN0YXBwIiwic2Vzc2lvbl9zdGF0ZSI6ImRiZjUyOGU1LWJlZWEtNDgxNS04NDhlLTdlNzY1MTIxNmFhYyIsImFjciI6IjAiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsidHVzZXIiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiZGVmYXVsdC1yb2xlcy1teW9pZGMiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIiwic2lkIjoiZGJmNTI4ZTUtYmVlYS00ODE1LTg0OGUtN2U3NjUxMjE2YWFjIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0dXNlciIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiJ9.SBHEfhRg2tHCYhE1HzJcQ03B2MNyKiHmxdAr9VHl7wEalY2kYdy_CIZotcpOLQJ7oj13dliIspX1sHY0XAyqHYOYek9a1G-tmhElrkzkST0DttqwaWaFzgHdpOfF_TMPRAjqeDe24g_6T7g7749QmR8ChhtN4c77xNhOSpSIVOIIRqEu6BD9y-9ccxENCs4rG8Ww5f7WQuVHF5I5cQe8qoYf15ne3fr8W2IvtXSeec09c5DT1RlUZ04RITX6GL81PI74qkQSokdFQLkwhdQ0Toxu2_odwZgvjVbYOObl2j1cpvKCSsUUNnaxuAa0C29p4iX7xk-PdQjBitQzeOdVGg",
    "expires_in": 299,
    "refresh_expires_in": 1799,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxZjI0YjA5OS1kOThkLTQxMzAtODM5ZC1kNTE0YWU1NjZiYWQifQ.eyJleHAiOjE2NzIwNzg0MjYsImlhdCI6MTY3MjA3NjYyNiwianRpIjoiNTRhMWEwZDUtZmQ0OC00MTI5LThhYzUtOWQ4NDY5ZGRjMTU3IiwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo4MDkwL3JlYWxtcy9teW9pZGMiLCJhdWQiOiJodHRwOi8vMTI3LjAuMC4xOjgwOTAvcmVhbG1zL215b2lkYyIsInN1YiI6IjNkNDY0Yzg0LWNkMTMtNDNjYi04MWI2LTJhNmU2ZWI5NjRlNSIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJ0ZXN0YXBwIiwic2Vzc2lvbl9zdGF0ZSI6ImRiZjUyOGU1LWJlZWEtNDgxNS04NDhlLTdlNzY1MTIxNmFhYyIsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwiLCJzaWQiOiJkYmY1MjhlNS1iZWVhLTQ4MTUtODQ4ZS03ZTc2NTEyMTZhYWMifQ.bY5ijVxi9TO8tLp6Gk3ZpvZzaTcHex6hj7Oh-OtqUBg",
    "token_type": "Bearer",
    "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJJNEQyVGp6WUZvSWRGeTlIRGE1aFk2a2tjclRmZUNodmhLODZuN2JXTWJRIn0.eyJleHAiOjE2NzIwNzY5MjYsImlhdCI6MTY3MjA3NjYyNiwiYXV0aF90aW1lIjoxNjcyMDc1ODY5LCJqdGkiOiJjMmFkYTM4NC1iNTdmLTRjNWQtYTUyZS1kZmQ4NWMyM2JjYjUiLCJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwOTAvcmVhbG1zL215b2lkYyIsImF1ZCI6InRlc3RhcHAiLCJzdWIiOiIzZDQ2NGM4NC1jZDEzLTQzY2ItODFiNi0yYTZlNmViOTY0ZTUiLCJ0eXAiOiJJRCIsImF6cCI6InRlc3RhcHAiLCJzZXNzaW9uX3N0YXRlIjoiZGJmNTI4ZTUtYmVlYS00ODE1LTg0OGUtN2U3NjUxMjE2YWFjIiwiYXRfaGFzaCI6ImF6V3NqR3FDMEFwajRRbGx6XzMtM1EiLCJhY3IiOiIwIiwic2lkIjoiZGJmNTI4ZTUtYmVlYS00ODE1LTg0OGUtN2U3NjUxMjE2YWFjIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0dXNlciIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiJ9.KeOK3Fa3gZHtOYe-j1YcG9fZ5DV09WGnm-118lWfrHqjAc3ZWKvguQqfGVQHjXum9obPQvu0-luUYvoFF4LghEbteFie9R83QvgW4palPOVFig2zTbSrP2eeXTk39hOiUG8RYbmYRygWSTA5poht3r77fuPiC5HohyqnRBLKmzRSqZQC6P_7bjbO1LOq8JB7NpCVKpJwZtSRkka5d3CHkGuLZiQuRh_Wo7B66bXJgDJ4b_okVaqR_M4m31WLpeEF9mkAyVQa5lVt1-keNj6y05cjiRuZewOXAomuDysJpyZEhZFREkz5ByuFHGn40D6mWWOriRq5QhorQTb81E1l4A",
    "not-before-policy": 0,
    "session_state": "dbf528e5-beea-4815-848e-7e7651216aac",
    "scope": "openid profile email"
}

还可以根据用户名和密码,同样能够获取access_token

curl --location --request POST 'http://127.0.0.1:8090/realms/myoidc/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=testapp' \
--data-urlencode 'client_secret=V1FTVZMIUAVx52n7VM5ndrCLqwj0pGTF' \
--data-urlencode 'username=tuser' \
--data-urlencode 'password=passwd' \
--data-urlencode 'grant_type=password'
{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJJNEQyVGp6WUZvSWRGeTlIRGE1aFk2a2tjclRmZUNodmhLODZuN2JXTWJRIn0.eyJleHAiOjE2NzIwNzcwODUsImlhdCI6MTY3MjA3Njc4NSwianRpIjoiY2YxNDczMjgtYjlhZC00MTgzLWFhNTMtYWJhMGE2MDRlNDllIiwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo4MDkwL3JlYWxtcy9teW9pZGMiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiM2Q0NjRjODQtY2QxMy00M2NiLTgxYjYtMmE2ZTZlYjk2NGU1IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdGFwcCIsInNlc3Npb25fc3RhdGUiOiJhMzY2ZGQ0MS02MGIzLTQ2YTYtOWI3Ni1lZTk0OWFkMmYwZWQiLCJhY3IiOiIxIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbInR1c2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtbXlvaWRjIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiYTM2NmRkNDEtNjBiMy00NmE2LTliNzYtZWU5NDlhZDJmMGVkIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0dXNlciIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiJ9.Tquqv50danvZWx3ZVln4PF7SPuO0cd1oiUn8bSY_UIWo7CeuiUkiUq0jdlUrJ2Y79hSHpGwJ5HBddhUOvlLJc-XkmED8iqvRPTROyLg7FzO_Y-QRVp29xjSf1S45x28q4-xiJN8zdAqnFCmBe5gjXhsDG7zcZGd9Gf1eqstkSzUM1CeYLLW0pjO6lLtwFsLArmB7G0LtB4xA2RHpfZZeSmeroHU6Ijbex2MF6oXhdtZSW072ZwJNSbAODB2VSWskMP5Be15grx40mY80e9ujYliWTbtGTm7qHueYOn99xh8-cLK04JcoZSyH-EtIe2pj-mi7ljpdSxiPGUCphYT-5A",
    "expires_in": 300,
    "refresh_expires_in": 1800,
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxZjI0YjA5OS1kOThkLTQxMzAtODM5ZC1kNTE0YWU1NjZiYWQifQ.eyJleHAiOjE2NzIwNzg1ODUsImlhdCI6MTY3MjA3Njc4NSwianRpIjoiOTVkMmVmZWItMGFhNi00ODk2LWFjZjEtMzU5ZGMxNjlkNjk2IiwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo4MDkwL3JlYWxtcy9teW9pZGMiLCJhdWQiOiJodHRwOi8vMTI3LjAuMC4xOjgwOTAvcmVhbG1zL215b2lkYyIsInN1YiI6IjNkNDY0Yzg0LWNkMTMtNDNjYi04MWI2LTJhNmU2ZWI5NjRlNSIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJ0ZXN0YXBwIiwic2Vzc2lvbl9zdGF0ZSI6ImEzNjZkZDQxLTYwYjMtNDZhNi05Yjc2LWVlOTQ5YWQyZjBlZCIsInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6ImEzNjZkZDQxLTYwYjMtNDZhNi05Yjc2LWVlOTQ5YWQyZjBlZCJ9.u72Fw2Us0YldlRfLXRRdy3PehtqKrrV6ciIy29gwy60",
    "token_type": "Bearer",
    "not-before-policy": 0,
    "session_state": "a366dd41-60b3-46a6-9b76-ee949ad2f0ed",
    "scope": "profile email"
}

根据access_token获取用户信息

curl --location --request GET 'http://127.0.0.1:8090/realms/myoidc/protocol/openid-connect/userinfo' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer xxxxxxxxxxxxInR5cCIgOiAiSldUIiwia2lkIiA6ICJJNEQyVGp6WUZvSWRGeTlIRGE1aFk2a2tjclRmZUNodmhLODZuN2JXTWJRIn0.eyJleHAiOjE2NzIwNzczOTksImlhdCI6MTY3MjA3NzA5OSwiYXV0aF90aW1lIjoxNjcyMDc1ODY5LCJqdGkiOiJmNzQ5NDU5Ny05NTg4LTQ0MDYtYTAzMC0yNzIxNzY3ZDY2YTciLCJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjgwOTAvcmVhbG1zL215b2lkYyIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiIzZDQ2NGM4NC1jZDEzLTQzY2ItODFiNi0yYTZlNmViOTY0ZTUiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJ0ZXN0YXBwIiwic2Vzc2lvbl9zdGF0ZSI6ImRiZjUyOGU1LWJlZWEtNDgxNS04NDhlLTdlNzY1MTIxNmFhYyIsImFjciI6IjAiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsidHVzZXIiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiZGVmYXVsdC1yb2xlcy1teW9pZGMiXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBwcm9maWxlIGVtYWlsIiwic2lkIjoiZGJmNTI4ZTUtYmVlYS00ODE1LTg0OGUtN2U3NjUxMjE2YWFjIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0dXNlciIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiJ9.TpHQBKxhVk1kPslB5uji8nJj3XeYMme3ocQkP3jhu37WoJOYGpAhHeT8PB4pEgPIju4PX_fBvmwU-cAWxmLeCR5S6W1p1LROTjx714b2F3NITLIufyEJXemkamabXbIkRZRsqjKGxPfXuwh6KPDfh3Lx7knbtRpNmXU0bbyx2ufb3T-qqRVoszJxYs1JeKPTw2ao1rpNs11vdQaHR-PkRkHR39PVBEICkIaTGKN2-eTOpbBa4FXFL_fBFIcIHfiAaclCqihZBjOkQpDgOIYTZ76yxl_oDYIB8GEQHpjcFeuf-IbsUcbKNaqDcprqE8EZTIAQt1ynV0Cisf9ZUgdIfg'
{
    "sub": "3d464c84-cd13-43cb-81b6-2a6e6eb964e5",
    "email_verified": false,
    "preferred_username": "tuser",
    "given_name": "",
    "family_name": ""
}

由此可见使用keycloak进行oidc认证的思路和demo中是一致的,甚至连请求方式都一样

nodejs应用集成keycloak

最后我们尝试将nodejs应用与keycloak进行集成

创建主函数,使用中间件集成keycloak

const path = require('path');
const express = require('express');
const session = require('express-session');
const favicon = require('serve-favicon');
const Keycloak = require('keycloak-connect');
const app = express();
const memoryStore = new session.MemoryStore();
 
app.set('view engine', 'ejs');
app.set('views', require('path').join(__dirname, '/view'));
app.use(express.static('static'));
app.use(favicon(path.join(__dirname, 'static', 'images', 'favicon.ico')));
app.use(session({
    secret: 'KWhjV<T=-*VW<;cC5Y6U-{F.ppK+])Ub',
    resave: false,
    saveUninitialized: true,
    store: memoryStore,
}));
 
const keycloak = new Keycloak({
    store: memoryStore,
});
 
app.use(keycloak.middleware({
    logout: '/logout',
    admin: '/',
}));
 
app.get('/', (req, res) => res.redirect('/home'));
 
const parseToken = raw => {
    if (!raw || typeof raw !== 'string') return null;
    try {
        raw = JSON.parse(raw);
        const token = raw.id_token ? raw.id_token : raw.access_token;
        const content = token.split('.')[1];
        return JSON.parse(Buffer.from(content, 'base64').toString('utf-8'));
    } catch (e) {
        console.error('Error while parsing token: ', e);
    }
};
 
app.get('/home', keycloak.protect(), (req, res, next) => {
    const details = parseToken(req.session['keycloak-token']);
    const embedded_params = {};
    if (details) {
        embedded_params.name = details.name;
        embedded_params.email = details.email;
        embedded_params.username = details.preferred_username;
    }
    res.render('home', {
        user: embedded_params,
    });
});
 
app.get('/login', keycloak.protect(), (req, res) => {
    return res.redirect('home');
});
 
app.get('/asset01', keycloak.enforcer(['asset-01:read'], {
    resource_server_id: 'my-application'
}), (req, res) => {
    return res.status(200).end('success');
});
 
app.get('/asset01/update', keycloak.enforcer(['asset-01:write'], {
    resource_server_id: 'my-application'
}), (req, res) => {
    return res.status(200).end('success');
});
 
app.use((req, res, next) => {
    return res.status(404).end('Not Found');
});
 
app.use((err, req, res, next) => {
    return res.status(req.errorCode ? req.errorCode : 500).end(req.error ? req.error.toString() : 'Internal Server Error');
});
 
const server = app.listen(3000, '127.0.0.1', () => {
    const host = server.address().address;
    const port = server.address().port;
    console.log('Application running at http://%s:%s', host, port);
});

导出keycloak配置文件

在这里插入图片描述

添加keycloak.json

{
  "realm": "myoidc",
  "auth-server-url": "http://127.0.0.1:8090/",
  "ssl-required": "external",
  "resource": "testapp",
  "credentials": {
    "secret": "V1FTVZMIUAVx52n7VM5ndrCLqwj0pGTF"
  },
  "confidential-port": 0
}

启动server

node index.js

访问127.0.0.1:3000然后自动跳转到keycloak登录界面,输入keycloak的用户名密码登录之后,由于没有权限会直接Access denied,需要在keycloak中授权

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/116692.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

cut与分层抽样(Stratified Sampling)

个人觉得&#xff0c; 把分层抽样称为“分类采样”会更贴切一些。通常最基本的采样手段是&#xff1a;随机抽样&#xff0c;但是在很多场景下&#xff0c;随机抽样是有问题的&#xff0c;举一个简单的例子&#xff1a;如果现在要发起一个啤酒品牌知名度的调查问卷&#xff0c;我…

二、let进阶、const、全部变量与顶层对象

二、let进阶、const、全部变量与顶层对象 一、let进阶 let创建了块级作用域&#xff0c;每次循环时内部的块级作用域都会去访问外层块级作用域中的变量i&#xff0c;而外层块级作用域中的变量i都不同&#xff0c;所以打印0-9&#xff1b;类似于闭包&#xff1a;内部函数返回到…

MySQL【Primary key】主键约束

关键字: [ primary key ] 作用&#xff1a;用来唯一标识表中的一行记录 特点&#xff1a;1.唯一性约束非空约束 唯一且为空 唯一性约束&#xff1a;不允许出现重复值 非空约束&#xff1a;不允许出现空值&#xff0c;但不是 NULL 2.一个表最多只能有一个主键约束&#x…

35岁之后软件测试工程师靠什么养家?我还能继续做测试。

35岁真是一个焦虑的年龄&#xff0c;我一个在北京软件测试的朋友从一个大公司裸辞以后&#xff0c;年前应聘到了一家小公司做技术总监&#xff0c;因为疫情的爆发&#xff0c;公司倒闭了&#xff0c;他失业了。为了养家我这个朋友不得不冒着被病毒感染的危险开始送外卖。作为一…

Springboot整合Liquibase初始化数据库

一、前言 liquibase是一个数据库变更的版本控制工具。项目中通过liquibase解析用户编写的liquibase的配置文件,生成sql语句&#xff0c;并执行和记录。执行是根据记录确定sql语句是否曾经执行过&#xff0c;和配置文件里的预判断语句确定sql是否执行。 本篇文章给大家介绍spr…

RabbitMQ浏览器UI插件

Awesome RabbitMQ Management 该插件中文意思是"很棒的 RabbitMQ 管理",是对原生RabbitMQ的UI图形界面进行增强的一款插件。 可在Google Chrome商店中下载安装 概述 原文介绍 Awesome RabbitMQ Management RabbitMQ queues view can become unusable with many qu…

微服务 Spring Boot Mybatis-Plus 整合 EasyPOI 实现 Excel 一对多 导入

文章目录⛄引言一、EasyPOI 实现Excel 的一对多导入 -- 代码实现⛅需求说明⚡核心源码实现二、Easy POI 实现一对多导入 -- 测试三、效果图展示⛵小结⛄引言 Excel导入 是 开发中 很常用的 功能 &#xff0c;本篇讲解 如何使用 Spring Boot MyBatis -Plus 整合 EasyPOI 实现E…

2023年淘宝天猫年货节超级红包哪里领?

2023年淘宝天猫年货节超级红包哪里领? 姐妹们在淘宝年货节活动就就就要开始预热模式了&#xff0c;时间是12月27日中午12点&#xff0c;大家最爱的项目一定是领取超级红包了。这不&#xff0c;2023年的年货节就要开启了。但是&#xff0c;很多小伙伴还不知道&#xff0c;淘宝…

<生产者、消费者问题>——《Linux》

目录 1. 生产者消费者模型 1.1 为何要使用生产者消费者模型 1.2 生产者消费者模型优点 2.基于BlockingQueue的生产者消费者模型 2.1 BlockingQueue 2.2 C queue模拟阻塞队列的生产消费模型 3.POSIX信号量 4.基于环形队列的生产消费模型 后记&#xff1a;●由于作者水平…

windows中docker修改镜像与容器存放目录(不想放c盘)

查看状态 wsl --list -v 先退出docker deskop 查看状态 wsl --list -v 关闭wsl wsl --shutdown docker的镜像与容器存放目录在此处 新建一个你想要存放的目录&#xff0c;比如我存到H盘这个地方 分别将c盘那两个文件夹&#xff08;data和distro&#xff09;的内容打包压缩到H盘…

php使用redis进行消息发布订阅

php使用redis进行消息发布订阅前置条件消费者订阅subscribe.php生产者发送消息publish.php执行消费者订阅&#xff0c;开始阻塞获取消息执行生产者&#xff0c;开始发送消息查看消费者终端前置条件 已经安装了php的redis扩展 消费者订阅subscribe.php <?php ini_set(def…

【Java基础】Java日志—什么是日志?什么是Log4j?Log4j入门案例及配置

目录 一、什么是日志&#xff1f; 二、为什么会有日志&#xff1f; 四、入门案例&#xff1a;Log4j日志信息输出到控制台 步骤与实现&#xff1a; 步骤1&#xff1a;拷贝坐标 步骤2&#xff1a;拷贝配置文件 log4j.properties 步骤3&#xff1a;编写测试类 写到最后 &…

TensorRt(3)mnist示例中的C++ API

目前sample中mnist提供了至少caffe、onnx的预训练模型&#xff0c;在TensorRT经过优化生成engine后再进行infer&#xff0c;两种模型的加载处理略有不同&#xff0c;做出简单api处理说明。 最后尝试使用最少的代码来实现整个流程。 文章目录1、主要的C API 定义2、minst示例2.1…

云安全类型及预防方法

恶意软件是我们必须面对的现实&#xff0c;我们每天都需要与蠕虫、病毒、间谍软件和其他行恶意软件作斗争&#xff0c;而云恶意软件是我们需要面对的又一种类别。它已经发展十多年&#xff0c;早在2011年就托管在亚马逊简单存储服务存储桶中。云安全提供商Netskope报告称&#…

springboot够用就好系列-2.基于commandfast框架的应用开发

参考web的jsoncat框架&#xff0c;实现一个控制台IO的commandfast简易框架&#xff0c;并进行使用。 目录 程序效果 实现过程 样例代码 工程文件 参考资料 程序效果 截图1.查询当前时间和用户&#xff0c;查询磁盘空间 利用commandfast框架&#xff0c;实现的2个简单功能&…

95后阿里P7晒出工资单:狠补了两眼泪汪汪,真香...

最近一哥们跟我聊天装逼&#xff0c;说他最近从阿里跳槽了&#xff0c;我问他跳出来拿了多少&#xff1f;哥们表示很得意&#xff0c;说跳槽到新公司一个月后发了工资&#xff0c;月入5万多&#xff0c;表示很满足&#xff01;这样的高薪资着实让人羡慕&#xff0c;我猜这是税后…

Redis 核心原理串讲(上),从一条请求透视高性能的本质

文章目录Redis 核心原理总览&#xff08;全局篇&#xff09;前言一、请求二、数据结构1. 有哪些&#xff1f;2. 为什么节省内存又高效&#xff1f;三、网络模型1、四种常见IO模型1.1 同步阻塞1.2 同步非阻塞1.3 IO多路复用1.4 异步IO2、事件驱动2.1 引子2.2 事件驱动模型3、Rea…

【Windows】win10家庭版无法被远程桌面(mstsc)连接的解决方案

&#x1f41a;作者简介&#xff1a;花神庙码农&#xff08;专注于Linux、WLAN、TCP/IP、Python等技术方向&#xff09;&#x1f433;博客主页&#xff1a;花神庙码农 &#xff0c;地址&#xff1a;https://blog.csdn.net/qxhgd&#x1f310;系列专栏&#xff1a;善假于物&#…

前端知识学习

一、html的学习 1.1 html的基本结构 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body></body> </html>1. <!DOCTYPE html> 告诉浏…

网页版chatGPT,国内直接打开就用的chatgpt

先看效果&#xff1a; 文件就是一个网页文件&#xff0c;直接打开就可以网页使用了。 使用的前提是需要有chatGPT的账号去获取apikey,然后把拿到的apikey放在下面代码中 然后网页的代码如下&#xff1a; <script src"https://unpkg.com/vue3/dist/vue.global.js&qu…