portal基于OIDC认证

1.OIDC介绍

OpenID Connect 简称为 OIDC,已成为 Internet 上单点登录和身份管理的通用标准。 它在 OAuth2 上构建了一个身份层,是一个基于 OAuth2 协议的身份认证标准协议。

OAuth2 实际上只做了授权,而 OpenID Connect 在授权的基础上又加上了认证。

OIDC 的优点是:简单的基于 JSON 的身份令牌(JWT),并且完全兼容 OAuth2 协议。

OpenID Connect 发布于 2014 年,是建立在 OAuth 2.0 协议之上的简单身份层,它允许客户端基于授权服务器或身份提供商(IdP)进行的身份验证来验证最终用户的身份,并获得用户的相关信息。 OpenID Connect 提供了 RESTful HTTP API,并使用 Json 作为数据的传递格式。

2.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信息

3.portal基于OIDC认证

3.1 安装keycloak

安装包下载:

wget https://github.com/keycloak/keycloak/releases/download/26.0.0/keycloak-26.0.0.tar.gz

启动命令:

cd /root/keycloak/keycloak-26.0.0/bin

keycloak 启动需要java:

java 安装方法参考:https://blog.csdn.net/lennonlau/article/details/146525998

配置环境变量

export JAVA_HOME=/usr/lib/jvm/jdk-17-oracle-x64
export PATH=$JAVA_HOME/bin:$PATH

创建管理员账号账号(安装后设置一次就行):

./kc.sh bootstrap-admin user

以上启动keycloak是不带证书的,需要带证书的,需要先生成证书 生成keycloak 需要的证书:

[root@CDN157 keycloak-26.0.0]# 
[root@CDN157 keycloak-26.0.0]# 
[root@CDN157 keycloak-26.0.0]# openssl genpkey -algorithm RSA -out privateKey.pem -pkeyopt rsa_keygen_bits:2048
......................................................................................................................................................................................+++
........................+++
[root@CDN157 keycloak-26.0.0]# openssl req -new -key privateKey.pem -out certificate.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:cn
State or Province Name (full name) []:beijing
Locality Name (eg, city) [Default City]:beijing
Organization Name (eg, company) [Default Company Ltd]:tmlake
Organizational Unit Name (eg, section) []:tmlake
Common Name (eg, your name or your server's hostname) []:keycloak
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
[root@CDN157 keycloak-26.0.0]# 
[root@CDN157 keycloak-26.0.0]# ls -ltr
总用量 28
-rw-r--r-- 1 root root    26 10月  4 2024 version.txt
-rw-r--r-- 1 root root   492 10月  4 2024 README.md
-rw-r--r-- 1 root root 11358 10月  4 2024 LICENSE.txt
drwxr-xr-x 5 root root    66 10月  4 2024 lib
drwxr-xr-x 2 root root    23 10月  4 2024 themes
drwxr-xr-x 2 root root    23 10月  4 2024 providers
drwxr-xr-x 3 root root    85 10月  4 2024 conf
-rw------- 1 root root     0 12月 22 11:39 nohup.out
drwxr-xr-x 4 root root    40 12月 22 11:40 data
drwxr-xr-x 3 root root   197 12月 22 14:02 bin
drwxr-xr-x 2 root root     6 12月 22 14:59 cert
-rw-r--r-- 1 root root  1704 12月 22 14:59 privateKey.pem
-rw-r--r-- 1 root root  1001 12月 22 15:01 certificate.csr
[root@CDN157 keycloak-26.0.0]# openssl x509 -req -days 365 -in certificate.csr -signkey privateKey.pem -out certificate.crt
Signature ok
subject=/C=cn/ST=beijing/L=beijing/O=tmlake/OU=tmlake/CN=keycloak
Getting Private key

配置到conf/keycloak.conf 中

# The file path to a server certificate or certificate chain in PEM format.
https-certificate-file=/root/clb/keycloak/keycloak-26.4.4/cert/certificate.crt

# The file path to a private key in PEM format.
https-certificate-key-file=/root/clb/keycloak/keycloak-26.4.4/cert/privateKey.pem

启动keycloak:

./kc.sh start-dev --http-port=8081

访问地址

http: http://ip:8081/

https: https://ip:8443/

其中ip 是部署keycloak机器的ip

3.2 安装njet

njet-4.0.2.0-1.el7.x86_64

portal_1.0.3.npk 和ssh_remote_mod.so 在other.tar.xz安装包里获取:

https://gitee.com/njet-rd/njet/releases/download/v4.0.2.0/other.tar.xz

yum localinstall  njet-4.0.2.0-1.el7.x86_64.rpm

更新数据库api_gateway.db 密码为123456

sudo /usr/local/njet/sbin/agw_passwd /usr/local/njet/apigw_data/api_gateway.db 123456

配置:

njet.conf:

worker_processes auto;

cluster_name njet;
node_name node1;

error_log logs/error.log info;

helper ctrl modules/njt_helper_ctrl_module.so conf/njet_ctrl.conf;
helper broker modules/njt_helper_broker_module.so;

load_module modules/njt_agent_dynlog_module.so;  
load_module modules/njt_dyn_ssl_module.so;
load_module modules/njt_http_vtsc_module.so;
load_module modules/njt_http_location_module.so;
load_module modules/njt_http_lua_module.so;
load_module modules/njt_http_dyn_upstream_module.so;
load_module modules/njt_http_dyn_server_module.so;  
load_module modules/njt_http_token_sync_module.so;
load_module modules/njt_http_upstream_member_module.so;
load_module modules/njt_http_dyn_lua_module.so;
load_module modules/njt_openapi_util_module.so; 
events {
    worker_connections  1024;
}

shared_slab_pool_size  100m;   
http {
    token_sync zone=token:4M sync_time=5s clean_time=10s;
    dyn_kv_conf conf/iot-work.conf;
    include mime.types;
    access_log logs/access.log;
    vhost_traffic_status_zone;
    lua_shared_dict mcp_message_bus 1M;
    lua_package_path "$prefix/lualib/lib/?.lua;$prefix/modules/?.lua;$prefix/apps/?.lua;;";
    lua_package_cpath "$prefix/lualib/clib/?.so;;";
    init_by_lua_block {
        local _=require("lor.index")
        local _=require("lsqlite3complete")
    }
    server {

        listen       8080 ssl;
        error_page 401 =302 /portal;
        client_max_body_size 1000m; 
        ssl_certificate          /usr/local/njet/conf/test.com+1.pem;
        ssl_certificate_key      /usr/local/njet/conf/test.com+1-key.pem;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5;

        location / {
           root html;
        }
        location /icons/ {
            alias /usr/local/njet/apps/__icons/;
            try_files $uri =404;
       }

       location /chat {
        proxy_pass http://192.168.40.73/chat;
        # 标准代理头
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # WebSocket 支持(如果聊天功能需要)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        # 超时设置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        # 缓冲区设置(根据实际需要调整)
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        # 错误处理
        proxy_next_upstream error timeout http_502 http_503 http_504;

    }
    location /_next {

        sub_filter "Powered by" "";
        proxy_pass http://192.168.40.73/_next;
        # 标准代理头
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # WebSocket 支持(如果聊天功能需要)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        # 超时设置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        # 缓冲区设置(根据实际需要调整)
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        # 错误处理
        proxy_next_upstream error timeout http_502 http_503 http_504;

    }
    location /console {
        proxy_pass http://192.168.40.73/console;
        # 标准代理头
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # WebSocket 支持(如果聊天功能需要)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        # 超时设置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        # 缓冲区设置(根据实际需要调整)
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        # 错误处理
        proxy_next_upstream error timeout http_502 http_503 http_504;

    }
    location /api {
        proxy_pass http://192.168.40.73/api;
        # 标准代理头
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # WebSocket 支持(如果聊天功能需要)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        # 超时设置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        # 缓冲区设置(根据实际需要调整)
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        # 错误处理
        proxy_next_upstream error timeout http_502 http_503 http_504;

    }

    location /logo {
        proxy_pass http://192.168.40.73/logo;
        # 标准代理头
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # WebSocket 支持(如果聊天功能需要)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        # 超时设置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        # 缓冲区设置(根据实际需要调整)
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
        # 错误处理
        proxy_next_upstream error timeout http_502 http_503 http_504;

    }

    }

    upstream llm_backend {
        server 0.0.0.0;
        balancer_by_lua_block {
            local balancer = require "njt.balancer"
            local cjson = require "cjson"
            local tokenLib = require("njt.token")
            
            local data = njt.req.get_body_data()

            local body_data = njt.req.get_body_data()
            if not body_data then
                njt.log(njt.ERR, "can not get body in balancer_by_lua")
            else
                local status, result = pcall(cjson.decode, body_data)
                if not status then
                    njt.log(njt.ERR, "request body JSON decode error:".. body_data)
                else    
                    local token_key = "param_end_user_id_uuid_" .. result.user
                    local rc, tv_str = tokenLib.token_get(token_key)
                    if rc ~= 0 or not tv_str or tv_str == "" then
                        njt.log(njt.ERR, "in balance_by_lua user id is null of end_user_id:".. result.user)
                    else
                        njt.ctx.userid = tv_str
                    end
                end
            end

            -- njt.log(njt.ERR, "=======upstream get body:", data)
            -- local key = data or njt.var.remote_addr
            -- local hash = njt.crc32_long(key) % 3
            local servers = {
                { ip = "192.168.0.209", port = 8080 }
            }
            local server = servers[1]
            local ok, err = balancer.set_current_peer(server.ip, server.port)
            if not ok then
                njt.log(njt.ERR, "Failed to set peer: ", err)
                return njt.exit(500)
            end
        }
    }

    client_body_in_single_buffer on;
    client_body_in_file_only off;
    client_max_body_size 10m;
    client_body_buffer_size 10m;

    server {
        listen 28080;

        location /v1 {
            mirror /llm_mirror;

            #proxy_pass http://192.168.0.209:8080;
            proxy_pass http://llm_backend;

            header_filter_by_lua_block{
                njt.ctx.res_tokens = 0
                njt.ctx.stream_flag = false

                local headers = njt.resp.get_headers()
                if headers["Content-Type"] then
                    if string.find(headers["Content-Type"], "event-stream", 1, true) ~= nil then
                        njt.ctx.stream_flag = true
                    end
                end
            }

            body_filter_by_lua_file /usr/local/njet/lua_filter/parse_response.lua;
        }
        
        location /llm_mirror {
            internal;
            content_by_lua_file /usr/local/njet/lua_filter/parse_request.lua;
        } 
     }

  server {
        listen 28088;
        location /njet_mcp {
                content_by_lua_file /usr/local/njet/apps/njet_mcp/njet_mcp.lua;
        }

  }
}

njet_ctrl.conf:

load_module modules/njt_http_sendmsg_module.so;
load_module modules/njt_ctrl_config_api_module.so; 
load_module modules/njt_helper_health_check_module.so;
load_module modules/njt_http_upstream_api_module.so; 
load_module modules/njt_http_location_api_module.so;
load_module modules/njt_doc_module.so;
load_module modules/njt_http_vtsd_module.so;
load_module modules/njt_http_lua_module.so;
load_module modules/njt_http_dyn_upstream_module.so;
load_module modules/njt_http_dyn_upstream_api_module.so;
load_module modules/njt_http_dyn_server_api_module.so; 
load_module modules/njt_http_upload_module.so;

error_log logs/error_ctrl.log info;

events {
    worker_connections  1024;
}

http {
    dyn_kv_conf conf/ctrl_kv.conf;
    lua_package_path "$prefix/lualib/lib/?.lua;$prefix/modules/?.lua;;";
    lua_package_cpath "$prefix/lualib/clib/?.so;;";
    init_by_lua_block {
        local _=require("lor.index")
        local _=require("lsqlite3complete")
    }
    include mime.types;
    access_log off;
    server {
        client_max_body_size 1000m;  
        listen       8081;

        location / {
            return 200 "njet control panel\n";
        }

        location /api {
            dyn_module_api;  
        }
        
        location /doc {
            doc_api;
        }
        
        location /metrics {
            vhost_traffic_status_display;
            vhost_traffic_status_display_format html;
        }

        location /api_gateway {
           access_by_lua_block {
              local ac=require("api_gateway.access.control")
              local access=ac.new("/api_gateway")
              access:check()
           }
           content_by_lua_block {
              local api_gateway=require("api_gateway")
              api_gateway.main()
           }
        }
    }
}

拷贝 ssh_remote_mod.so

sudo cp ssh_remote_mod.so /usr/local/njet/lualib/clib/

启动njet:

sudo systemctl start njet 

将下载后的 portal_1.0.3.npk 文件上传到 njet 主机上

NJET_SITE="http://localhost:8081"

PORTAL_FILE=$(curl -k -s -F "file=@portal_1.0.3.npk"  "$NJET_SITE/api/v1/upload" |jq -r '.file')
echo $PORTAL_FILE
TOKEN=$(curl -k -X POST -d '{"login_data":{"username": "agw_admin", "password": "********"}}' -s "$NJET_SITE/api_gateway/auth/login" | jq -r '.token')
echo $TOKEN
curl -X 'POST' \
  "$NJET_SITE/api_gateway/deploy/app" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"uploaded_file\": \"$PORTAL_FILE\"}"
  

浏览器输入:https://ip:8080/portal/ 进入portal 页面,在portal 页面 应用管理 页面可以上传需要部署的应用。

其中ip为部署njet 的机器

3.3 keycloak页面配置realm

img img

img img

3.4 keycloak页面配置client id

img img

img img

img img

img img

3.5 keycloak页面配置users

初始配置时user页面如果没有mobile 属性,需要增加自由属性mobile

3.5.1 初始配置增加user页面mobile 属性,步骤如下:

img img

img img

3.5.2 为属性添加映射

img img

img img

img img

img img

img img

保存之后通过access token获取userinfo时就可以得到配置的该属性,该属性值在user页面可以设置

img img

img img

3.6 keycloak页面配置groups

img img

img img

3.7 keycloak页面将用户添加到上一步建的group 中

img img

3.8 将group mapping映射到scope映射关系是在clients的应用里映射,名称为groups,勾选add to id token和add to userinfo

img img

img img

img img

img img

img img

3.9 keycloak 地址配置:Keyclock 相关的 url, client_id, client_secret 可以存到 sys_config 表需要预先配置进去

字段名称 value 类型 描述
keycloak_realm tmlake string Keycloak 得realm, 对应portal的domain空格拆分可以保存多个,暂时使用一个
keycloak_token_url https://192.168.40.73:8443/realms/tmlake/protocol/openid-connect/token string 获取token url
keycloak_userinfo_url https://192.168.40.73:8443/realms/tmlake/protocol/openid-connect/userinfo string 获取用户信息url
keycloak_client_id bigModel string keycloak应用,此处创建的叫bigModel
keycloak_client_secret RjdLTWVacktEV2d4aGVKeHVXZ0huNDJJUHRVRnFLeFE= password Client secret, keycloak自动生成, 此处不直接存储明文,目前使用base64简单加密,使用时在decode此处原值为F7KMeZrKDWgxheJxuWgHn42IPtUFqKxQ,进行base64加密后为RjdLTWVacktEV2d4aGVKeHVXZ0huNDJJUHRVRnFLeFE= base64可以这么生成(要使用-n参数,会去掉末尾的换行符)echo -n ’’ | base64
keycloak_scope openid,groups string 权限范围,此处包含用户基本信息和用户group信息,示例如下{“sub”:“c23afa76-c1d0-4980-ab00-9c041409b034”,“email_verified”:false,“max_tokens”:“5000”,“name”:“a bc”,“mobile”:“18201331979”,“groups”:["/guest_group"],“preferred_username”:“abc”,“given_name”:“a”,“family_name”:“bc”,“email”:“abc@tmlake.com”}
keycloak_grant_type password string keycloak授权方式,用户名密码方式

获取token:

curl -X 'POST' \
  'http://192.168.40.215:8081/api_gateway/auth/login' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "api_group_name": "app",
  "login_type": "internal",
  "login_data": {
    "username": "agw_admin",
    "password": "123456"
  }
}'
返回:

{"token":"c644f5ee-19b7-4e24-8110-1c0ce22a8bfe","roles":[{"desc":"API Gateway admin ","id":1,"name":"agw_admin"}],"msg":"success","code":0,"user":{"name":"agw_admin","nickname":"","id":1}}

keycloak_realm配置:

curl -X 'POST' \
  'http://192.168.40.215:8081/api_gateway/conf/sysconfig' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer c644f5ee-19b7-4e24-8110-1c0ce22a8bfe' \
  -H 'Content-Type: application/json' \
  -d '[
  {
    "config_key": "keycloak_realm",
    "config_value": "njet.com",
    "config_type": "string"
  }
]'


{
  "msg": "success",
  "data": "",
  "code": 0
}

keycloak_token_url配置:

curl -X 'POST' \
  'http://192.168.40.215:8081/api_gateway/conf/sysconfig' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer c644f5ee-19b7-4e24-8110-1c0ce22a8bfe' \
  -H 'Content-Type: application/json' \
  -d '[
  {
    "config_key": "keycloak_token_url",
    "config_value": "https://部署keycloak机器的ip:8443/realms/njet.com.cn/protocol/openid-connect/token",
    "config_type": "string"
  }
]'

{
  "msg": "success",
  "data": "",
  "code": 0
}

keycloak_userinfo_url 配置:

curl -X 'POST' \
  'http://192.168.40.215:8081/api_gateway/conf/sysconfig' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer c644f5ee-19b7-4e24-8110-1c0ce22a8bfe' \
  -H 'Content-Type: application/json' \
  -d '[
  {
    "config_key": "keycloak_userinfo_url",
    "config_value": "https://部署keycloak机器的ip:8443/realms/njet.com.cn/protocol/openid-connect/userinfo",
    "config_type": "string"
  }
]'



{
  "msg": "success",
  "data": "",
  "code": 0
}

keycloak_client_id 配置:

curl -X 'POST' \
  'http://192.168.40.215:8081/api_gateway/conf/sysconfig' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer c644f5ee-19b7-4e24-8110-1c0ce22a8bfe' \
  -H 'Content-Type: application/json' \
  -d '[
  {
    "config_key": "keycloak_client_id",
    "config_value": "njetclient",
    "config_type": "string"
  }
]'

{
  "msg": "success",
  "data": "",
  "code": 0
}

keycloak_client_secret 配置:

img img

limin@limin-B660-VH:/usr/local/njet$ echo -n 'F7KMeZrKDWgxheJxuWgHn42IPtUFqKxQ' | base64
RjdLTWVacktEV2d4aGVKeHVXZ0huNDJJUHRVRnFLeFE=
curl -X 'POST' \
  'http://192.168.40.215:8081/api_gateway/conf/sysconfig' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer c644f5ee-19b7-4e24-8110-1c0ce22a8bfe' \
  -H 'Content-Type: application/json' \
  -d '[
  {
    "config_key": "keycloak_client_secret",
    "config_value": "RjdLTWVacktEV2d4aGVKeHVXZ0huNDJJUHRVRnFLeFE=",
    "config_type": "string"
  }
]'



{
  "msg": "success",
  "data": "",
  "code": 0
}

keycloak_scope 配置:

curl -X 'POST' \
  'http://192.168.40.215:8081/api_gateway/conf/sysconfig' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer c644f5ee-19b7-4e24-8110-1c0ce22a8bfe' \
  -H 'Content-Type: application/json' \
  -d '[
  {
    "config_key": "keycloak_scope",
    "config_value": "openid",
    "config_type": "string"
  }
]'

{
  "msg": "success",
  "data": "",
  "code": 0
}

keycloak_grant_type 配置:

curl -X 'POST' \
  'http://192.168.40.215:8081/api_gateway/conf/sysconfig' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer c644f5ee-19b7-4e24-8110-1c0ce22a8bfe' \
  -H 'Content-Type: application/json' \
  -d '[
  {
    "config_key": "keycloak_grant_type",
    "config_value": "password",
    "config_type": "string"
  }
]'

{
  "msg": "success",
  "data": "",
  "code": 0
}

3.10 登录portal页面,选中keycloak登录,验证是否可登录进去,log 日志有没有报错

img img

keycloak 选中后会刷新 domain 列表接口