Keycloak 13 自定義使用者身份認證流程(User Storage SPI)

Zhang_Xiang發表於2021-05-17

Keycloak

版本:13.0.0

spring-boot 專案 Github
user-storage-spi 專案 Github

介紹

Keycloak 是為現代應用程式和服務提供的一個開源的身份和訪問管理的解決方案。

Keycloak 在測試環境可以使用內嵌資料庫,生產環境需要重新配置資料庫。以下將一一介紹如何使用內嵌資料庫、重新配置資料庫。

特別需要注意 Keycloak 是在 WildFly 上構建的。

安裝

系統要求

  • Java8 JDK
  • 至少 512M 記憶體
  • 至少 1G 磁碟
  • 如果要設定 Keycloak 叢集則需要資料庫,比如:PostgreSQL、Oracle、MySQL 等
  • 如果要執行叢集,需要網路支援廣播。當然也可以不需要,只不過需要更改一堆配置

目錄結構

  • bin/ —— 各種啟動服務、伺服器上執行管理的指令碼
  • domain/ —— 叢集模式下的配置檔案和工作目錄
  • modules/ —— 服務使用的 Java 包
  • standalone/ —— 單機模式下的配置檔案和工作目錄
  • standalone/deployments/ —— 你自定義的擴充套件檔案
  • themes/ —— 介面主題檔案

使用 Docker 安裝 Keycloak

  1. 啟動容器

    docker run -p 10010:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin quay.io/keycloak/keycloak:13.0.0
    
  2. 成功啟動後訪問:http://127.0.0.1:10010/

  3. 登入 Keycloak 服務

    a. 點選 Administration Console

    b. 輸入賬號:admin,密碼:admin

    c. 進入控制檯介面

使用

建立 realm 和 使用者

建立一個 realm 和使用者以訪問內建的賬戶管理控制檯。把 realm 想像成租戶的概念。

  • Master realm —— 這個 realm 是初始化時建立的,它包含超級管理員。使用這個 realm 管理其它的 realm。
  • Other realm —— 超級管理員建立的 realm 。在這些 realm 裡,超級管理員建立使用者和應用程式,使用者擁有應用程式。

建立 realm

  1. 點選 Add realm 按鈕

  2. 輸入 realm 名稱

  3. 點選 create 按鈕完成建立
    點選建立完成 demo realm 建立。

建立使用者

  1. 切換 realm 到 Demo

  2. 建立使用者,Users -> Add User,輸入 Username ,點選 Save 按鈕

  3. 設定密碼

新建使用者登入控制檯

  1. 退出 admin 賬戶登入
  2. 地址輸入:http://127.0.0.1:10010/auth/realms/demo/account/,點選 Sign In 按鈕,使用新建立的使用者 Zhang 登入
  3. 設定新密碼
  4. 登入成功

Spring-Boot 認證

註冊客戶端到 Keycloak 中

應用程式或服務為了能使用 Keycloak,必須在 Keycloak 中註冊一個客戶端。可以通過超級管理員介面註冊,客戶端也可以自己通過 Keycloak 服務註冊。

客戶端註冊服務提供內建支援:Keycloak Client Representations、OIDC 客戶端後設資料、SAML 實體描述。客戶端註冊服務地址是:/auth/realms/<realm>/clients-registrations/<provider>

內建的 provider

  • default —— Keycloak Client Representation(JSON)
  • install —— Keycloak Adapter Configuration(JSON)
  • openid-connect —— OIDC 客戶端後設資料描述(JSON)
  • saml2-entity-descriptor —— SAML 實體描述者(XML)

認證

呼叫客戶端註冊服務需要令牌。令牌可以是 bearer 令牌,初始化訪問令牌或者註冊令牌。不需要令牌註冊客戶端也可以,但是需要配置客戶端註冊策略。

Bearer 令牌

Bearer 令牌可以代表使用者或者服務賬戶。呼叫端點需要以下許可權:

  • create-client 或者 manage-client —— 建立客戶端
  • view-client 或者 manage-client —— 檢視客戶端
  • manage-client —— 更新或者刪除客戶端

如果使用 Bearer 令牌建立客戶端,推薦使用來自服務賬戶(create-client 角色)的令牌。

初始化訪問令牌

推薦使用初始化訪問令牌註冊客戶端。初始化訪問令牌只能用於建立客戶端,並且可以配置有效期,同時可以配置可以建立多少客戶端。

初始化訪問令牌可以通過超級管理員控制檯建立。

點選儲存,完成令牌的建立。

當點選儲存以後會生成令牌,這個令牌如果忘記複製了,那麼就只能重新建立了。使用 bearer 令牌:

Authorization: bearer eyJhbGciOiJSUz...
註冊訪問令牌

當通過客戶端註冊服務建立客戶端時,返回值會包含一個註冊訪問令牌。註冊訪問令牌提供檢索客戶端配置、更新或者刪除客戶端的許可權。註冊訪問令牌使用方式和 bear 令牌、初始化訪問令牌的使用方式是一樣的。註冊訪問令牌是一次性的,當它使用時,返回值裡總會包含一個新的令牌。

如果客戶端在客戶端註冊服務之外建立,註冊訪問令牌就不會和客戶端關聯起來了。但可以在超級管理員控制檯生成註冊訪問令牌。

Keycloak Representations

default 客戶端註冊提供商可以建立、檢索、更新、刪除客戶端。使用 Keycloak Client Representation 轉換提供配置客戶端支援,就像在超級管理員控制檯配置的一樣。

建立 Client Representation(JSON)執行 HTTP POST 請求 /auth/realms/<realm>/clients-registrations/default

檢索 Client Representation 執行 GET 請求/auth/realms/<realm>/clients-registrations/default/<client id>

它也會返回新的註冊訪問令牌。

要更新 Client Representation 執行 HTTP PUT 請求 /auth/realms/<realm>/clients-registrations/default/<client id>

它也會返回一個新的註冊訪問令牌。

要刪除 Client Representation 執行 HTTP DELETE 請求 /auth/realms/<realm>/clients-registrations/default/<client id>

Keycloak 介面卡配置

installation 客戶端註冊提供商可以用於為客戶端獲取介面卡配置。除了令牌身份驗證之外,還可以使用 HTTP basic 認證(通過客戶端憑證)。使用下列請求頭以完成 HTTP basic 認證:

Authorization: basic BASE64(client-id + ':' + client-secret)

要獲取介面卡配置執行 HTTP GET 請求:/auth/realms/<realm>/clients-registrations/install/<client id>

公共客戶端不需要身份認證。這意味著 JavaScript 介面卡可以通過以上 URL 直接從 Keycloak 載入客戶端配置。

OIDC 動態客戶端註冊

終端在 Keycloak 中註冊客戶端 /auth/realms/<realm>/clients-registrations/openid-connect[/<client id>]

在 OIDC 發現中可以為 realm 找到終端,/auth/realms/<realm>/.well-known/openid-configuration

客戶端註冊策略

Keycloak 當前支援兩種方式註冊客戶端(通過客戶端註冊服務)。

  • 認證請求 —— 註冊客戶端請求要麼包含初始化訪問令牌,要麼包含 Bearer 令牌
  • 匿名請求 —— 註冊客戶端不需要包含任何令牌

匿名客戶端註冊請求是非常有趣和強大的功能,任何人都可以註冊客戶端並且沒有限制。因此提出了客戶端註冊策略 SPI,它提供了一個限制的方式(誰能註冊,在什麼條件下)。

在 Keycloak 超級管理員控制檯中,你可以看到匿名請求策略配置和認證請求策略配置。

當前支援的策略:

  • Trusted Hosts Policy —— 可以配置信任的 host 和域名。預設的,沒有白名單 host,所以匿名客戶端註冊實際上是禁用的。
  • Consent Required Policy —— 新註冊的客戶端 Consent Allowed 開關是啟動的。所以身份認證成功後,使用者將看到准許會話(如果需要的話)。
  • Protocol Mapper Policy —— 允許配置協議對映實現白名單。
  • Client Scope Policy —— 允許 Client Scopes 白名單,用於新註冊的客戶端或者更新的客戶端。
  • Full Scope Policy —— 新註冊的客戶端 Full Scope Allowed開關是關閉的。意味這這些客戶端沒有任何 realm 角色或者客戶端角色。
  • Max Clients Policy —— 如果註冊的客戶端數量在 realm 中大於或等於設定值將被駁回。預設值 200。
  • Client Disabled Policy —— 新註冊客戶端是禁用的。意味著超級管理員需要手動通過和啟用新註冊的客戶端。這個策略預設不啟用。

管理客戶端

客戶端是使用者請求身份認證的實體。客戶端有兩種格式。第一種是單點登入的(SSO)。另一種是型別是獲取訪問令牌然後代表使用者訪問服務。

OIDC 客戶端

建立 OIDC 客戶端
  1. 客戶端列表

  2. 新增客戶端,Client ID 是客戶端的身份標識。下一步選擇客戶端協議 openid-connect

    Client ID
    資料字母字串,用於客戶端身份識別(當 OIDC 請求時)。

    Name
    客戶端名稱。

    Description
    客戶端描述。

    Enabled
    如果關閉,客戶端將不允許請求驗證。

    Consent Required
    如果開啟,使用者將得到一個准許頁面(用於詢問使用者是否授權應用程式訪問)。頁面同時顯示客戶端要訪問的後設資料資訊,使用者可以看到客戶端要訪問的資訊。

    Access Type
    OIDC 客戶端型別。

    • confidential:機密訪問型別用於服務端客戶端(需要執行瀏覽器登入和需要客戶端密碼)。這個型別用於服務端應用程式。
    • public:Public 訪問型別是客戶端型別客戶端(需要執行瀏覽器登入)。客戶端型別應用程式沒有安全儲存祕密的方式。相反,通過為客戶端配置正確的重定向 URI 來限制訪問非常重要。
    • bearer-only:Bearer-only 訪問型別意味著應用程式僅允許 bearer 令牌請求。如果開啟這個,應用程式不能參與瀏覽器登入。

    Standard Flow Enabled
    如果開啟這個,客戶端將使用 OIDC 授權碼工作流。

    Implicit Flow Enabled
    如果開啟這個,客戶端將使用 OIDC 隱式工作流。

    Direct Access Grants Enabled
    如果開啟這個,客戶端將使用 OIDC 直接訪問授權。

    OAuth 2.0 Device Authorization Grant Enabled
    如果開啟這個,客戶端將使用 OIDC 裝置授權許可。

    OpenID Connect Client Initiated Backchannel Authentication Grant Enabled
    如果開啟這個,客戶端將使用 OIDC 客戶端初始化後端渠道認證許可。

    Root URL
    如果 Keycloak 不使用任何相對 URL,這個值就是預留值。

    Valid Redirect URIs
    這個是必填欄位。輸入 URL 模版然後點選 + 號新增。點選 - 號移除 URL。記住,最後還要點選 Sava 按鈕。萬用字元 * 只能用於 URI 的末端,例如:http://host.com/*

    註冊重定向 URL 模版時,你應該考慮避免被攻擊。

    Base URL
    如果 Keycloak 要連結客戶端,這個值需要設定。

    Admin URL
    為 Keycloak 指定客戶端介面卡,這個值是客戶端回撥終端。Keycloak 服務將使用這個 URI 回撥(比如:推送取消策略、執行後端渠道退出登入、其它超級管理員操作)。對於 Keycloak servlet 介面卡來說,這個值是 servlet 應用程式的 root URL。

    Web Origins
    這個設定是以 CORS 為中心。

  3. 保密客戶端證照
    如果客戶端 access type 設定為 confidential 時,頁面將會顯示 Credentials 標籤。注意,選擇 Confidential 標籤要儲存以後才會能看到 Credentials標籤。

    Client Authenticator 下拉框指定你的加密客戶端證照型別。預設是 Client Id and Secret。secret 自動生成,並且 Regenerate Secret 按鈕可以重新生成 secret。

    此外,可以選擇 Signed Jwt 或者 X509 Certificate 驗證代替 secret。

    Signed JWT

    當選擇 Signed Jwt 型別時,你需要為客戶端生成私鑰和證照。私鑰用於 JWT 簽名,證照用於服務端驗證簽名。點選 Generate new keys and certificate 按鈕生成私鑰和證照。

    也可以使用其它工具生成,然後匯入。

  4. 新建 Spring-boot 專案

  5. 建立 Keycloak 客戶端,導航到 http://127.0.0.1:10010/,切換到 Demo realm ,點選 Clients 選單,點選 Create 按鈕,建立一個 Demo realm 下的客戶端。客戶端 ID 為:spring-boot-toy。選擇 Access Typeconfidential

  6. 建立角色 toy-admin,並給使用者賦予角色


  7. Spring 專案新增 Maven 引用

    <?xml version="1.0" encoding="UTF-8"?>
     <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
         <modelVersion>4.0.0</modelVersion>
         <parent>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-parent</artifactId>
             <version>2.3.10.RELEASE</version>
             <relativePath/> <!-- lookup parent from repository -->
         </parent>
         <groupId>com.toy.keycloak</groupId>
         <artifactId>toy-keycloak</artifactId>
         <version>0.0.1-SNAPSHOT</version>
         <name>toy-keycloak</name>
    
         <properties>
             <java.version>1.8</java.version>
         </properties>
         <dependencies>
             <dependency>
                 <groupId>org.keycloak</groupId>
                 <artifactId>keycloak-spring-boot-starter</artifactId>
             </dependency>
    
             <dependency>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-devtools</artifactId>
                 <scope>runtime</scope>
                 <optional>true</optional>
             </dependency>
             <dependency>
                 <groupId>org.projectlombok</groupId>
                 <artifactId>lombok</artifactId>
                 <optional>true</optional>
             </dependency>
             <dependency>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-starter-test</artifactId>
                 <scope>test</scope>
                 <exclusions>
                     <exclusion>
                         <groupId>org.junit.vintage</groupId>
                         <artifactId>junit-vintage-engine</artifactId>
                     </exclusion>
                 </exclusions>
             </dependency>
         </dependencies>
    
         <dependencyManagement>
             <dependencies>
                 <dependency>
                     <groupId>org.keycloak.bom</groupId>
                     <artifactId>keycloak-adapter-bom</artifactId>
                     <version>13.0.0</version>
                     <type>pom</type>
                     <scope>import</scope>
                 </dependency>
             </dependencies>
         </dependencyManagement>
    
         <build>
             <plugins>
                 <plugin>
                     <groupId>org.springframework.boot</groupId>
                     <artifactId>spring-boot-maven-plugin</artifactId>
                     <configuration>
                         <excludes>
                             <exclude>
                                 <groupId>org.projectlombok</groupId>
                                 <artifactId>lombok</artifactId>
                             </exclude>
                         </excludes>
                     </configuration>
                 </plugin>
             </plugins>
         </build>
    
     </project>
    
    
  8. 暴露 Api 介面

     package com.toy.keycloak.webapi;
    
     import org.springframework.web.bind.annotation.GetMapping;
     import org.springframework.web.bind.annotation.RequestMapping;
     import org.springframework.web.bind.annotation.RestController;
    
     /**
     * @author Zhang_Xiang
     * @since 2021/5/11 16:56:57
     */
     @RestController
     @RequestMapping("temp")
     public class TempController {
    
         @GetMapping("weather")
         public String weather(){
             return "晴天☀️";
         }
    
     }
    
    
  9. 配置

    使用 Tomcat、Undertow、Jetty 不需要額外配置。在 application.properties 中配置 Keycloak 如下:

     keycloak.realm=demo  # realm 名稱
     keycloak.auth-server-url=http://127.0.0.1:10010/auth   # Keycloak 基礎服務地址
     keycloak.ssl-required=external
     keycloak.resource=spring-boot-toy   # 應用程式客戶端 ID
     keycloak.credentials.secret=91437668-b8f8-425b-ba9d-38439115dfbc
     keycloak.use-resource-role-mappings=true
     keycloak.securityConstraints[0].authRoles[0]=toy-admin
     keycloak.security-constraints[0].securityCollections[0].patterns[0]=/*
    

    設定 keycloak.enabled = false 可以停用 Keycloak 。

  10. 啟動 Spring 應用程式,並訪問 http://localhost:8080/temp/weather

    輸入使用者名稱 zhang,密碼123456

  11. 管理登入使用者會話,使用者登入後,進入 Keycloak 管理後臺,切換到 Demo realm 可以看到目前登入的會話列表。

    點選 Logout all 按鈕,所有使用者都將退出登入。

使用外部資料庫

Keycloak 內嵌了 H2 記憶體資料庫。Keycloak 預設使用 H2 持久化資料。H2 資料庫不適用於高併發場景並且不適用於叢集。

Keycloak 使用兩層技術持久化關係資料。底層技術是 JDBC。JDBC 用於連線 RDBMS。每個資料庫提供商都有不同的 JDBC 驅動。頂層技術用於持久化的是 Hibernate JPA。

使用者儲存 SPI

使用使用者儲存 SPI 擴充套件 Keycloak 以連線外部使用者資料和證照儲存。當 Keycloak 執行時查詢使用者時,比如使用者登入,Keycloak 執行幾個步驟定位使用者。首先看使用者是在在使用者快取中,然後在本地資料庫中查詢,如果找不到,迴圈使用者儲存 SPI 執行查詢。

使用者儲存 SPI 提供商實現打包和部署和 Java EE 元件相似。預設不啟用元件,如果要啟用需要在 Keycloak 超級管理員控制檯介面中配置 User Feberation

打包和部署

使用者儲存提供商打包為 JAR 然後部署到 Keycloak 執行時或者從 Keycloak 執行時取消部署,就像 WildFly 應用程式服務部署服務一樣。你也可以直接拷貝 JAR 包到 standalone/deployments/ 伺服器目錄,或者使用 JBoss CLI 執行部署。

為了 Keycloak 能識別服務提供商,你需要新增一個檔案到 JAR 包中:META-INF/services/org.keycloak.storage.UserStorageProviderFactory。這個檔案必須包含實現 UserStorageProviderFactory 類的全路徑:

org.keycloak.examples.federation.properties.ClasspathPropertiesStorageFactory
org.keycloak.examples.federation.properties.FilePropertiesStorageFactory

自定義使用者儲存 Provider

使用已有使用者資料庫。

MysqlUserStorageProvider 實現了很多介面,實現 UserStorageProvider 是實現是 SPI 的基本要求,換句話說,不實現 UserStorageProvider 就不能實現 SPI,UserLookupProvider 用於實現從外部資料庫查詢使用者以實現使用者登入,UserQueryProvider 定義複雜查詢以查詢使用者,CredentialInputValidator 驗證不同的證照型別(比如:驗證登入密碼)。

  1. provider

    public class MysqlUserStorageProvider implements UserLookupProvider, UserQueryProvider, CredentialInputValidator, UserStorageProvider {
    
        protected KeycloakSession session;
        protected ComponentModel model;
    
        public MysqlUserStorageProvider(KeycloakSession session, ComponentModel model) {
            this.session = session;
            this.model = model;
        }
    
        ...
    }
    
    
  2. ProviderFactory,這裡的 ProviderConfigProperty 用於自定義標籤,換句話說,在這裡定義了標籤以後,可以在 Keycloak 管理介面設定值,程式碼可以直接讀取到這些值。新增自定義 SPI 時,要留意新增在什麼 realm 下。

    public class MysqlUserStorageProviderFactory implements UserStorageProviderFactory<MysqlUserStorageProvider> {
    
    protected final List<ProviderConfigProperty> configMetadata;
    
    public MysqlUserStorageProviderFactory() {
        configMetadata = ProviderConfigurationBuilder.create()
                .property()
                .name(CONFIG_KEY_JDBC_DRIVER)
                .label("JDBC Driver Class")
                .type(ProviderConfigProperty.STRING_TYPE)
                .defaultValue("org.h2.Driver")
                .helpText("Fully qualified class name of the JDBC driver")
                .add()
                .property()
                .name(CONFIG_KEY_JDBC_URL)
                .label("JDBC URL")
                .type(ProviderConfigProperty.STRING_TYPE)
                .defaultValue("jdbc:h2:mem:customdb")
                .helpText("JDBC URL used to connect to the user database")
                .add()
                .property()
                .name(CONFIG_KEY_DB_USERNAME)
                .label("Database User")
                .type(ProviderConfigProperty.STRING_TYPE)
                .helpText("Username used to connect to the database")
                .add()
                .property()
                .name(CONFIG_KEY_DB_PASSWORD)
                .label("Database Password")
                .type(ProviderConfigProperty.STRING_TYPE)
                .helpText("Password used to connect to the database")
                .secret(true)
                .add()
                .property()
                .name(CONFIG_KEY_VALIDATION_QUERY)
                .label("SQL Validation Query")
                .type(ProviderConfigProperty.STRING_TYPE)
                .helpText("SQL query used to validate a connection")
                .defaultValue("select 1")
                .add()
                .build();
    }
    
    @Override
    public MysqlUserStorageProvider create(KeycloakSession session, ComponentModel model) {
        return new MysqlUserStorageProvider(session, model);
    }
    
    @Override
    public String getId() {
        return "user-center-provider";
    }
    
    @Override
    public List<ProviderConfigProperty> getConfigProperties() {
        return configMetadata;
    }
    
    @Override
    public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException {
        try (Connection c = DbUtil.getConnection(config)) {
            System.out.println(config.get(CONFIG_KEY_VALIDATION_QUERY));
            c.createStatement().execute(config.get(CONFIG_KEY_VALIDATION_QUERY));
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
            throw new ComponentValidationException("Unable to validate database connection", ex);
        }
    }
    }
    


  3. 檢視使用者列表

Spring-boot 使用外部使用者認證

  1. application.properties

     keycloak.realm=demo
     keycloak.auth-server-url=http://127.0.0.1:10010/auth
     keycloak.ssl-required=external
     keycloak.resource=spring-boot-toy
     keycloak.credentials.secret=91437668-b8f8-425b-ba9d-38439115dfbc
     keycloak.use-resource-role-mappings=true
     keycloak.verify-token-audience=true
     keycloak.securityConstraints[0].authRoles[0]=user
     keycloak.security-constraints[0].securityCollections[0].patterns[0]=/temp/weather
    
    • resource:客戶端 ID
    • use-resource-role-mappings:當設定為 true 時,OIDC Java 介面卡將 Token 中查詢應用程式級使用者角色對映。設定為 false 時,將從 realm 裡查詢使用者角色對映。預設設定為 false。
    • ssl-required:確保所有和 Keycloak 通訊的請求是 HTTPS,生產環境應設定為 all,預設值是 external,即外部請求需要 HTTPS,可選值是:allexternalnone
    • verify-token-audience:設定為 true 時,Bearer Token 進行身份認證時,介面卡會驗證令牌是否包含客戶端名稱。啟用將改善安全性(推薦)。

Keycloak 角色

一個 Realm 有多個客戶端,一個客戶端有多個使用者。在 Keycloak 有3種角色:

  • Realm Role:全域性角色,屬於指定 realm。任何客戶端都可以訪問這個角色,並且把這個角色對映到任何使用者。
  • Client Role:屬於指定客戶端的角色。只能對映到該客戶端下的使用者。
  • Composite Role:多個角色的組合。

在客戶端設定中啟用 Service Accounts Enabled,在 demo Realm 中新增 ordinary_user 角色。

Service Account Roles 標籤中新增 ordinary_user 角色。

新增該角色以後,切換到 User 標籤,可以看到使用者中已經關聯了角色。

使用資料庫使用者登入

  1. 新增角色限制 ordinary_user

    keycloak.realm=demo
    keycloak.auth-server-url=http://127.0.0.1:10010/auth
    keycloak.ssl-required=external
    keycloak.resource=spring-boot-toy
    keycloak.credentials.secret=91437668-b8f8-425b-ba9d-38439115dfbc
    keycloak.use-resource-role-mappings=true
    keycloak.verify-token-audience=true
    keycloak.securityConstraints[0].authRoles[0]=toy-admin
    keycloak.security-constraints[0].auth-roles[1]=ordinary_user
    keycloak.security-constraints[0].securityCollections[0].patterns[0]=/temp/weather
    
  2. 啟動服務,訪問 localhost:8080/temp/weather,輸入自定義資料庫中的使用者、密碼,以訪問介面。

相關文章