From f8e8528943c13b72df4c7cf7b8f4b9c5b704a3c4 Mon Sep 17 00:00:00 2001 From: traky Date: Mon, 6 Jan 2025 14:56:06 +0800 Subject: [PATCH 1/5] improve plugin doc and update tutorial --- docs/en/latest/plugins/openid-connect.md | 273 ++++++------- docs/en/latest/tutorials/keycloak-oidc.md | 467 ++++++++++++++++++++++ docs/zh/latest/plugins/openid-connect.md | 309 +++++++------- docs/zh/latest/tutorials/keycloak-oidc.md | 467 ++++++++++++++++++++++ 4 files changed, 1194 insertions(+), 322 deletions(-) create mode 100644 docs/en/latest/tutorials/keycloak-oidc.md create mode 100644 docs/zh/latest/tutorials/keycloak-oidc.md diff --git a/docs/en/latest/plugins/openid-connect.md b/docs/en/latest/plugins/openid-connect.md index 0ee7d7eb86b4..6545e296e6d9 100644 --- a/docs/en/latest/plugins/openid-connect.md +++ b/docs/en/latest/plugins/openid-connect.md @@ -5,7 +5,7 @@ keywords: - API Gateway - OpenID Connect - OIDC -description: OpenID Connect allows the client to obtain user information from the identity providers, such as Keycloak, Ory Hydra, Okta, Auth0, etc. API Gateway APISIX supports to integrate with the above identity providers to protect your APIs. +description: The openid-connect Plugin supports the integration with OpenID Connect (OIDC) identity providers, such as Keycloak, Auth0, Microsoft Entra ID, Google, Okta, and more. It allows APISIX to authenticate clients and obtain their information from the identity provider before allowing or denying their access to upstream protected resources. --- + + + + ## Description -[OpenID Connect](https://openid.net/connect/) (OIDC) is an authentication protocol based on the OAuth 2.0. It allows the client to obtain user information from the identity provider (IdP), e.g., Keycloak, Ory Hydra, Okta, Auth0, etc. API Gateway Apache APISIX supports to integrate with the above identity providers to protect your APIs. +The `openid-connect` Plugin supports the integration with [OpenID Connect (OIDC)](https://openid.net/connect/) identity providers, such as Keycloak, Auth0, Microsoft Entra ID, Google, Okta, and more. It allows APISIX to authenticate clients and obtain their information from the identity provider before allowing or denying their access to upstream protected resources. ## Attributes -| Name | Type | Required | Default | Valid values | Description | +| Name | Type | Required | Default | Valid values | Description | |--------------------------------------|----------|----------|-----------------------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| client_id | string | True | | | OAuth client ID. | -| client_secret | string | True | | | OAuth client secret. | -| discovery | string | True | | | Discovery endpoint URL of the identity server. | -| scope | string | False | "openid" | | OIDC scope that corresponds to information that should be returned about the authenticated user, also known as [claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims). The default value is `openid`, the required scope for OIDC to return a `sub` claim that uniquely identifies the authenticated user. Additional scopes can be appended and delimited by spaces, such as `openid email profile`. | -| required_scopes | string[] | False | | | Array of strings. Used in conjunction with the introspection endpoint (when `bearer_only` is `true`). If present, the plugin will check if the token contains all required scopes. If not, 403 will be returned with an error message | -| realm | string | False | "apisix" | | Realm used for authentication. | -| bearer_only | boolean | False | false | | When set to `true`, APISIX will only check if the authorization header in the request matches a bearer token. | -| logout_path | string | False | "/logout" | | Path for logging out. | -| post_logout_redirect_uri | string | False | | | URL to redirect to after logging out. If the OIDC discovery endpoint does not provide an [`end_session_endpoint`](https://openid.net/specs/openid-connect-rpinitiated-1_0.html), the plugin internally redirects using the [`redirect_after_logout_uri`](https://github.com/zmartzone/lua-resty-openidc). Otherwise, it redirects using the [`post_logout_redirect_uri`](https://openid.net/specs/openid-connect-rpinitiated-1_0.html). | -| redirect_uri | string | False | | | URI to which the identity provider redirects back to. If not configured, APISIX will append the `.apisix/redirect` suffix to determine the default `redirect_uri`. Note that the provider should be properly configured to allow such `redirect_uri` values. | -| timeout | integer | False | 3 | [1,...] | Request timeout time in seconds. | -| ssl_verify | boolean | False | false | | When set to true, verifies the identity provider's SSL certificates. | -| introspection_endpoint | string | False | | | URL of the token verification endpoint of the identity server. | -| introspection_endpoint_auth_method | string | False | "client_secret_basic" | | Authentication method name for token introspection. | -| token_endpoint_auth_method | string | False | | | Authentication method name for token endpoint. The default will get the first supported method specified by the OP. | -| public_key | string | False | | | Public key to verify the token. | -| use_jwks | boolean | False | false | | When set to `true`, uses the JWKS endpoint of the identity server to verify the token. | -| use_pkce | boolean | False | false | | when set to `true`, the "Proof Key for Code Exchange" as defined in RFC 7636 will be used. | -| token_signing_alg_values_expected | string | False | | | Algorithm used for signing the authentication token. | -| set_access_token_header | boolean | False | true | | When set to true, sets the access token in a request header. By default, the `X-Access-Token` header is used. | -| access_token_in_authorization_header | boolean | False | false | | When set to true and `set_access_token_header` is also true, sets the access token in the `Authorization` header. | -| set_id_token_header | boolean | False | true | | When set to true and the ID token is available, sets the ID token in the `X-ID-Token` request header. | -| set_userinfo_header | boolean | False | true | | When set to true and the UserInfo object is available, sets it in the `X-Userinfo` request header. | -| set_refresh_token_header | boolean | False | false | | When set to true and a refresh token object is available, sets it in the `X-Refresh-Token` request header. | -| session | object | False | | | When bearer_only is set to false, openid-connect will use Authorization Code flow to authenticate on the IDP, so you need to set the session-related configuration. | -| session.secret | string | True | Automatic generation | 16 or more characters | The key used for session encrypt and HMAC operation. | -| session.cookie | object | False | | | | +| client_id | string | True | | | OAuth client ID. | +| client_secret | string | True | | | OAuth client secret. | +| discovery | string | True | | | URL to the well-known discovery document of the OpenID provider, which contains a list of OP API endpoints. The Plugin can directly utilize the endpoints from the discovery document. You can also configure these endpoints individually, which takes precedence over the endpoints supplied in the discovery document. | +| scope | string | False | openid | | OIDC scope that corresponds to information that should be returned about the authenticated user, also known as [claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims). This is used to authorize users with proper permission. The default value is `openid`, the required scope for OIDC to return a `sub` claim that uniquely identifies the authenticated user. Additional scopes can be appended and delimited by spaces, such as `openid email profile`. | +| required_scopes | array[string] | False | | | Scopes required to be present in the access token. Used in conjunction with the introspection endpoint when `bearer_only` is `true`. If any required scope is missing, the Plugin rejects the request with a 403 forbidden error. | +| realm | string | False | apisix | | Realm in [`WWW-Authenticate`](https://www.rfc-editor.org/rfc/rfc6750#section-3) response header accompanying a 401 unauthorized request due to invalid bearer token. | +| bearer_only | boolean | False | false | | If true, strictly require bearer access token in requests for authentication. | +| logout_path | string | False | /logout | | Path to activate the logout. | +| post_logout_redirect_uri | string | False | | | URL to redirect users to after the `logout_path` receive a request to log out. | +| redirect_uri | string | False | | | URI to redirect to after authentication with the OpenID provider. Note that the redirect URI should not be the same as the request URI, but a sub-path of the request URI. For example, if the `uri` of the Route is `/api/v1/*`, `redirect_uri` can be configured as `/api/v1/redirect`. If `redirect_uri` is not configured, APISIX will append `/.apisix/redirect` to the request URI to determine the value for `redirect_uri`. | +| timeout | integer | False | 3 | [1,...] | Request timeout time in seconds. | +| ssl_verify | boolean | False | false | | If true, verify the OpenID provider 's SSL certificates. | +| introspection_endpoint | string | False | | | URL of the [token introspection](https://datatracker.ietf.org/doc/html/rfc7662) endpoint for the OpenID provider used to introspect access tokens. If this is unset, the introspection endpoint presented in the well-known discovery document is used [as a fallback](https://github.com/zmartzone/lua-resty-openidc/commit/cdaf824996d2b499de4c72852c91733872137c9c). | +| introspection_endpoint_auth_method | string | False | client_secret_basic | | Authentication method for the token introspection endpoint. The value should be one of the authentication methods specified in the `introspection_endpoint_auth_methods_supported` [authorization server metadata](https://www.rfc-editor.org/rfc/rfc8414.html) as seen in the well-known discovery document, such as `client_secret_basic`, `client_secret_post`, `private_key_jwt`, and `client_secret_jwt`. | +| token_endpoint_auth_method | string | False | client_secret_basic | | Authentication method for the token endpoint. The value should be one of the authentication methods specified in the `token_endpoint_auth_methods_supported` [authorization server metadata](https://www.rfc-editor.org/rfc/rfc8414.html) as seen in the well-known discovery document, such as `client_secret_basic`, `client_secret_post`, `private_key_jwt`, and `client_secret_jwt`. If the configured method is not supported, fall back to the first method in the `token_endpoint_auth_methods_supported` array. | +| public_key | string | False | | | Public key used to verify JWT signature id asymmetric algorithm is used. Providing this value to perform token verification will skip token introspection in client credentials flow. You can pass the public key in `-----BEGIN PUBLIC KEY-----\\n……\\n-----END PUBLIC KEY-----` format. | +| use_jwks | boolean | False | false | | If true and if `public_key` is not set, use the JWKS to verify JWT signature and skip token introspection in client credentials flow. The JWKS endpoint is parsed from the discovery document. | +| use_pkce | boolean | False | false | | If true, use the Proof Key for Code Exchange (PKCE) for Authorization Code Flow as defined in [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636). | +| token_signing_alg_values_expected | string | False | | | Algorithm used for signing JWT, such as `RS256`. | +| set_access_token_header | boolean | False | true | | If true, set the access token in a request header. By default, the `X-Access-Token` header is used. | +| access_token_in_authorization_header | boolean | False | false | | If true and if `set_access_token_header` is also true, set the access token in the `Authorization` header. | +| set_id_token_header | boolean | False | true | | If true and if the ID token is available, set the value in the `X-ID-Token` request header. | +| set_userinfo_header | boolean | False | true | | If true and if user info data is available, set the value in the `X-Userinfo` request header. | +| set_refresh_token_header | boolean | False | false | | If true and if the refresh token is available, set the value in the `X-Refresh-Token` request header. | +| session | object | False | | | Session configuration used when `bearer_only` is `false` and the Plugin uses Authorization Code flow. | +| session.secret | string | True | | 16 or more characters | Key used for session encryption and HMAC operation when `bearer_only` is `false`. It is automatically generated and saved to etcd if not configured. When using APISIX in the standalone mode where etcd is no longer the configuration center, the `secret` should be configured. | +| session.cookie | object | False | | | Cookie configurations. | | session.cookie.lifetime | integer | False | 3600 | | Cookie lifetime in seconds. | -| unauth_action | string | False | "auth" | ["auth","deny","pass"] | Specify the response type on unauthenticated requests. "auth" redirects to identity provider, "deny" results in a 401 response, "pass" will allow the request without authentication. | -| proxy_opts | object | False | | | HTTP proxy that the OpenID provider is behind. | -| proxy_opts.http_proxy | string | False | | http://proxy-server:port | HTTP proxy server address. | -| proxy_opts.https_proxy | string | False | | http://proxy-server:port | HTTPS proxy server address. | -| proxy_opts.http_proxy_authorization | string | False | | Basic [base64 username:password] | Default `Proxy-Authorization` header value to be used with `http_proxy`. Can be overridden with custom `Proxy-Authorization` request header. | -| proxy_opts.https_proxy_authorization | string | False | | Basic [base64 username:password] | Default `Proxy-Authorization` header value to be used with `https_proxy`. Cannot be overridden with custom `Proxy-Authorization` request header since with with HTTPS the authorization is completed when connecting. | -| proxy_opts.no_proxy | string | False | | | Comma separated list of hosts that should not be proxied. | -| authorization_params | object | False | | | Additional parameters to send in the request to the authorization endpoint. | -| client_rsa_private_key | string | False | | | Client RSA private key used to sign JWT. | -| client_rsa_private_key_id | string | False | | | Client RSA private key ID used to compute a signed JWT. | -| client_jwt_assertion_expires_in | integer | False | 60 | | Life duration of the signed JWT in seconds. | +| unauth_action | string | False | auth | ["auth","deny","pass"] | Action for unauthenticated requests. When set to `auth`, redirect to the authentication endpoint of the OpenID provider. When set to `pass`, allow the request without authentication. When set to `deny`, return 401 unauthenticated responses rather than start the authorization code grant flow. | +| proxy_opts | object | False | | | Configurations for the proxy server that the OpenID provider is behind. | +| proxy_opts.http_proxy | string | False | | | Proxy server address for HTTP requests, such as `http://:`. | +| proxy_opts.https_proxy | string | False | | | Proxy server address for HTTPS requests, such as `http://:`. | +| proxy_opts.http_proxy_authorization | string | False | | Basic [base64 username:password] | Default `Proxy-Authorization` header value to be used with `http_proxy`. Can be overriden with custom `Proxy-Authorization` request header. | +| proxy_opts.https_proxy_authorization | string | False | | Basic [base64 username:password] | Default `Proxy-Authorization` header value to be used with `https_proxy`. Cannot be overriden with custom `Proxy-Authorization` request header since with HTTPS, the authorization is completed when connecting. | +| proxy_opts.no_proxy | string | False | | | Comma separated list of hosts that should not be proxied. | +| authorization_params | object | False | | | Additional parameters to send in the request to the authorization endpoint. | +| client_rsa_private_key | string | False | | | Client RSA private key used to sign JWT for authentication to the OP. Required when `token_endpoint_auth_method` is `private_key_jwt`. | +| client_rsa_private_key_id | string | False | | | Client RSA private key ID used to compute a signed JWT. Optional when `token_endpoint_auth_method` is `private_key_jwt`. | +| client_jwt_assertion_expires_in | integer | False | 60 | | Life duration of the signed JWT for authentication to the OP, in seconds. Used when `token_endpoint_auth_method` is `private_key_jwt` or `client_secret_jwt`. | | renew_access_token_on_expiry | boolean | False | true | | If true, attempt to silently renew the access token when it expires or if a refresh token is available. If the token fails to renew, redirect user for re-authentication. | | access_token_expires_in | integer | False | | | Lifetime of the access token in seconds if no `expires_in` attribute is present in the token endpoint response. | | refresh_session_interval | integer | False | | | Time interval to refresh user ID token without requiring re-authentication. When not set, it will not check the expiration time of the session issued to the client by the gateway. If set to 900, it means refreshing the user's id_token (or session in the browser) after 900 seconds without requiring re-authentication. | @@ -87,153 +91,110 @@ description: OpenID Connect allows the client to obtain user information from th | jwk_expires_in | integer | False | 86400 | | Expiration time for JWK cache in seconds. | | jwt_verification_cache_ignore | boolean | False | false | | If true, force re-verification for a bearer token and ignore any existing cached verification results. | | cache_segment | string | False | | | Optional name of a cache segment, used to separate and differentiate caches used by token introspection or JWT verification. | -| introspection_interval | integer | False | 0 | | TTL of the cached and introspected access token in seconds. | -| introspection_expiry_claim | string | False | | | Name of the expiry claim, which controls the TTL of the cached and introspected access token. The default value is 0, which means this option is not used and the plugin defaults to use the TTL passed by expiry claim defined in `introspection_expiry_claim`. If `introspection_interval` is larger than 0 and less than the TTL passed by expiry claim defined in `introspection_expiry_claim`, use `introspection_interval`. | -| introspection_addon_headers | string[] | False | | | Array of strings. Used to append additional header values to the introspection HTTP request. If the specified header does not exist in origin request, value will not be appended. | +| introspection_interval | integer | False | 0 | | TTL of the cached and introspected access token in seconds. The default value is 0, which means this option is not used and the Plugin defaults to use the TTL passed by expiry claim defined in `introspection_expiry_claim`. If `introspection_interval` is larger than 0 and less than the TTL passed by expiry claim defined in `introspection_expiry_claim`, use `introspection_interval`. | +| introspection_expiry_claim | string | False | exp | | Name of the expiry claim, which controls the TTL of the cached and introspected access token. | +| introspection_addon_headers | array[string] | False | | | Used to append additional header values to the introspection HTTP request. If the specified header does not exist in origin request, value will not be appended. | NOTE: `encrypt_fields = {"client_secret"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields). -## Scenarios +## Examples + +The examples below demonstrate how you can configure the `openid-connect` Plugin for different scenarios. + +:::note -:::tip +You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command: -Tutorial: [Use Keycloak with API Gateway to secure APIs](https://apisix.apache.org/blog/2022/07/06/use-keycloak-with-api-gateway-to-secure-apis/) +```bash +admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g') +``` ::: -This plugin offers two scenarios: +### Authorization Code Flow -1. Authentication between Services: Set `bearer_only` to `true` and configure the `introspection_endpoint` or `public_key` attribute. In this scenario, APISIX will reject requests without a token or invalid token in the request header. +The authorization code flow is defined in [RFC 6749, Section 4.1](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1). It involves exchanging an temporary authorization code for an access token, and is typically used by confidential and public clients. -2. Authentication between Browser and Identity Providers: Set `bearer_only` to `false.` After successful authentication, this plugin can obtain and manage the token in the cookie, and subsequent requests will use the token. In this mode, the user session will be stored in the browser as a cookie and this data is encrypted, so you have to set a key for encryption via `session.secret`. +The following diagram illustrates the interaction between different entities when you implement the authorization code flow: -### Token introspection +![Authorization code flow diagram](https://static.api7.ai/uploads/2023/11/27/Ga2402sb_oidc-code-auth-flow-revised.png) -[Token introspection](https://www.oauth.com/oauth2-servers/token-introspection-endpoint/) validates a request by verifying the token with an OAuth 2.0 authorization server. +When an incoming request does not contain an access token in its header nor in an appropriate session cookie, the Plugin acts as a relying party and redirects to the authorization server to continue the authorization code flow. -You should first create a trusted client in the identity server and generate a valid JWT token for introspection. +After successful authentication, the Plugin keeps the token in the session cookie, and subsequent requests will use the token stored in the cookie. -The image below shows an example token introspection flow via a Gateway: +See [Implement Authorization Code Grant](../tutorials/keycloak-oidc.md#implement-authorization-code-grant) for an example to use the `openid-connect` Plugin to integrate with Keycloak using the authorization code flow. -![token introspection](https://raw.githubusercontent.com/apache/apisix/master/docs/assets/images/plugin/oauth-1.png) +### Proof Key for Code Exchange (PKCE) -The example below shows how you can enable the Plugin on Route. The Route below will protect the Upstream by introspecting the token provided in the request header: +The Proof Key for Code Exchange (PKCE) is defined in [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636). PKCE enhances the authorization code flow by adding a code challenge and verifier to prevent authorization code interception attacks. -:::note -You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command: +The following diagram illustrates the interaction between different entities when you implement the authorization code flow with PKCE: -```bash -admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g') -``` +![Authorization code flow with PKCE diagram](https://static.api7.ai/uploads/2024/11/04/aJ2ZVuTC_auth-code-with-pkce.png) -::: +See [Implement Authorization Code Grant](../tutorials/keycloak-oidc.md#implement-authorization-code-grant) for an example to use the `openid-connect` Plugin to integrate with Keycloak using the authorization code flow with PKCE. -```bash -curl http://127.0.0.1:9180/apisix/admin/routes/5 -H "X-API-KEY: $admin_key" -X PUT -d ' -{ - "uri": "/get", - "plugins":{ - "openid-connect":{ - "client_id": "${CLIENT_ID}", - "client_secret": "${CLIENT_SECRET}", - "discovery": "${DISCOVERY_ENDPOINT}", - "introspection_endpoint": "${INTROSPECTION_ENDPOINT}", - "bearer_only": true, - "realm": "master", - "introspection_endpoint_auth_method": "client_secret_basic" - } - }, - "upstream":{ - "type": "roundrobin", - "nodes":{ - "httpbin.org:443":1 - } - } -}' -``` +### Client Credential Flow -Now, to access the Route: +The client credential flow is defined in [RFC 6749, Section 4.4](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4). It involves clients requesting an access token with its own credentials to access protected resources, typically used in machine to machine authentication and is not on behalf of a specific user. -```bash -curl -i -X GET http://127.0.0.1:9080/get -H "Host: httpbin.org" -H "Authorization: Bearer {JWT_TOKEN}" -``` +The following diagram illustrates the interaction between different entities when you implement the client credential flow: -In this example, the Plugin enforces that the access token and the Userinfo object be set in the request headers. +
+Client credential flow diagram +
+
-When the OAuth 2.0 authorization server returns an expire time with the token, it is cached in APISIX until expiry. For more details, read: +See [Implement Client Credentials Grant](../tutorials/keycloak-oidc.md#implement-client-credentials-grant) for an example to use the `openid-connect` Plugin to integrate with Keycloak using the client credentials flow. -1. [lua-resty-openidc](https://github.com/zmartzone/lua-resty-openidc)'s documentation and source code. -2. `exp` field in the RFC's [Introspection Response](https://tools.ietf.org/html/rfc7662#section-2.2) section. +### Introspection Flow -### Introspecting with public key +The introspection flow is defined in [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662). It involves verifying the validity and details of an access token by querying an authorization server’s introspection endpoint. -You can also provide the public key of the JWT token for verification. If you have provided a public key and a token introspection endpoint, the public key workflow will be executed instead of verification through an identity server. This is useful if you want to reduce network calls and speedup the process. +In this flow, when a client presents an access token to the resource server, the resource server sends a request to the authorization server’s introspection endpoint, which responds with token details if the token is active, including information like token expiration, associated scopes, and the user or client it belongs to. -The example below shows how you can add public key introspection to a Route: +The following diagram illustrates the interaction between different entities when you implement the authorization code flow with token introspection: -```bash -curl http://127.0.0.1:9180/apisix/admin/routes/5 -H "X-API-KEY: $admin_key" -X PUT -d ' -{ - "uri": "/get", - "plugins":{ - "openid-connect":{ - "client_id": "${CLIENT_ID}", - "client_secret": "${CLIENT_SECRET}", - "discovery": "${DISCOVERY_ENDPOINT}", - "bearer_only": true, - "realm": "master", - "token_signing_alg_values_expected": "RS256", - "public_key": "-----BEGIN PUBLIC KEY----- - {public_key} - -----END PUBLIC KEY-----" - } - }, - "upstream":{ - "type": "roundrobin", - "nodes":{ - "httpbin.org:443":1 - } - } -}' -``` +
+
+Client credential with introspection diagram +
+
-In this example, the Plugin can only enforce that the access token should be set in the request headers. +See [Implement Client Credentials Grant](../tutorials/keycloak-oidc.md#implement-client-credentials-grant) for an example to use the `openid-connect` Plugin to integrate with Keycloak using the client credentials flow with token introspection. -### Authentication through OIDC Relying Party flow +### Password Flow -When an incoming request does not contain an access token in its header nor in an appropriate session cookie, the Plugin can act as an OIDC Relying Party and redirect to the authorization endpoint of the identity provider to go through the [OIDC authorization code flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth). +The password flow is defined in [RFC 6749, Section 4.3](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3). It is designed for trusted applications, allowing them to obtain an access token directly using a user’s username and password. In this grant type, the client app sends the user’s credentials along with its own client ID and secret to the authorization server, which then authenticates the user and, if valid, issues an access token. -Once the user has authenticated with the identity provider, the Plugin will obtain and manage the access token and further interaction with the identity provider. The access token will be stored in a session cookie. +Though efficient, this flow is intended for highly trusted, first-party applications only, as it requires the app to handle sensitive user credentials directly, posing significant security risks if used in third-party contexts. -The example below adds the Plugin with this mode of operation to the Route: +The following diagram illustrates the interaction between different entities when you implement the password flow: -```bash -curl http://127.0.0.1:9180/apisix/admin/routes/5 -H "X-API-KEY: $admin_key" -X PUT -d ' -{ - "uri": "/get", - "plugins": { - "openid-connect": { - "client_id": "${CLIENT_ID}", - "client_secret": "${CLIENT_SECRET}", - "discovery": "${DISCOVERY_ENDPOINT}", - "bearer_only": false, - "realm": "master" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "httpbin.org:443": 1 - } - } -}' -``` +
+Password flow diagram +
+
+ +See [Implement Password Grant](../tutorials/keycloak-oidc.md#implement-password-grant) for an example to use the `openid-connect` Plugin to integrate with Keycloak using the password flow. -In this example, the Plugin can enforce that the access token, the ID token, and the UserInfo object to be set in the request headers. +### Refresh Token Grant + +The refresh token grant is defined in [RFC 6749, Section 6](https://datatracker.ietf.org/doc/html/rfc6749#section-6). It enables clients to request a new access token without requiring the user to re-authenticate, using a previously issued refresh token. This flow is typically used when an access token expires, allowing the client to maintain continuous access to resources without user intervention. Refresh tokens are issued along with access tokens in certain OAuth flows and their lifespan and security requirements depend on the authorization server’s configuration. + +The following diagram illustrates the interaction between different entities when implementing password flow with refresh token flow: + +
+Password grant with refresh token flow diagram +
+
+ +See [Refresh Token](../tutorials/keycloak-oidc.md#refresh-token) for an example to use the `openid-connect` Plugin to integrate with Keycloak using the password flow with token refreshes. ## Troubleshooting -This section covers a few commonly seen issues when working with this plugin to help you troubleshoot. +This section covers a few commonly seen issues when working with this Plugin to help you troubleshoot. ### APISIX Cannot Connect to OpenID provider @@ -251,15 +212,15 @@ the error request to the redirect_uri path, but there's no session state found A common misconfiguration is to configure the `redirect_uri` the same as the URI of the route. When a user initiates a request to visit the protected resource, the request directly hits the redirection URI with no session cookie in the request, which leads to the no session state found error. -To properly configure the redirection URI, make sure that the `redirect_uri` matches the route where the plugin is configured, without being fully identical. For instance, a correct configuration would be to configure `uri` of the route to `/api/v1/*` and the path portion of the `redirect_uri` to `/api/v1/redirect`. +To properly configure the redirection URI, make sure that the `redirect_uri` matches the Route where the Plugin is configured, without being fully identical. For instance, a correct configuration would be to configure `uri` of the Route to `/api/v1/*` and the path portion of the `redirect_uri` to `/api/v1/redirect`. You should also ensure that the `redirect_uri` include the scheme, such as `http` or `https`. #### 2. Missing Session Secret -If you deploy APISIX in the [standalone mode](../deployment-modes.md#standalone), make sure that `session.secret` is configured. +If you deploy APISIX in the [standalone mode](/apisix/production/deployment-modes#standalone-mode), make sure that `session.secret` is configured. -User sessions are stored in browser as cookies and encrypted with session secret. The secret is automatically generated and saved to etcd if no secret is configured through the `session.secret` attribute. However, in standalone mode, etcd is no longer the configuration center. Therefore, you should explicitly configure `session.secret` for this plugin in the YAML configuration center `apisix.yaml`. +User sessions are stored in browser as cookies and encrypted with session secret. The secret is automatically generated and saved to etcd if no secret is configured through the `session.secret` attribute. However, in standalone mode, etcd is no longer the configuration center. Therefore, you should explicitly configure `session.secret` for this Plugin in the YAML configuration center `apisix.yaml`. #### 3. Cookie Not Sent or Absent @@ -278,3 +239,11 @@ If so, try adjusting `proxy_buffers`, `proxy_buffer_size`, and `proxy_busy_buffe #### 5. Invalid Client Secret Verify if `client_secret` is valid and correct. An invalid `client_secret` would lead to an authentication failure and no token shall be returned and stored in session. + +#### 6. PKCE IdP Configuration + +If you are enabling PKCE with the authorization code flow, make sure you have configured the IdP client to use PKCE. For example, in Keycloak, you should configure the PKCE challenge method in the client's advanced settings: + +
+PKCE keycloak configuration +
diff --git a/docs/en/latest/tutorials/keycloak-oidc.md b/docs/en/latest/tutorials/keycloak-oidc.md new file mode 100644 index 000000000000..ac9c58b44992 --- /dev/null +++ b/docs/en/latest/tutorials/keycloak-oidc.md @@ -0,0 +1,467 @@ +--- +title: Set Up SSO with Keycloak (OIDC) +keywords: + - APISIX + - API Gateway + - OIDC + - Keycloak +description: This article describes how to integrate APISIX with Keycloak using the authorization code grant, client credentials grant, and password grant, using the openid-connect Plugin. +--- + + + +[OpenID Connect (OIDC)](https://openid.net/connect/) is a simple identity layer on top of the [OAuth 2.0 protocol](https://www.rfc-editor.org/rfc/rfc6749). It allows clients to verify the identity of end users based on the authentication performed by the identity provider, as well as to obtain basic profile information about end users in an interoperable and REST-like manner. With APISIX and [Keycloak](https://www.keycloak.org/), you can implement OIDC-based authentication processes to protect your APIs and enable single sign-on (SSO). + +[Keycloak](https://www.keycloak.org/) is an open-source identity and access management solution for modern applications and services. Keycloak supports single sign-on (SSO), which enables services to interface with Keycloak through protocols such as OIDC and OAuth 2.0. In addition, Keycloak also supports delegating authentication to third party identity providers such as Facebook and Google. + +This tutorial will show you how to integrate APISIX with Keycloak using [authorization code grant](#implement-authorization-code-grant), [client credentials grant](#implement-client-credentials-grant), and [password grant](#implement-password-grant), using the [`openid-connect`](/hub/openid-connect) Plugin. + +## Configure Keycloak + +Start a Keycloak instance named `apisix-quickstart-keycloak` with the administrator name `quickstart-admin` and password `quickstart-admin-pass` in [development mode](https://www.keycloak.org/server/configuration#_starting_keycloak_in_development_mode) in Docker. The exposed port is mapped to `8080` on the host machine: + +```shell +docker run -d --name "apisix-quickstart-keycloak" \ + -e 'KEYCLOAK_ADMIN=quickstart-admin' \ + -e 'KEYCLOAK_ADMIN_PASSWORD=quickstart-admin-pass' \ + -p 8080:8080 \ + quay.io/keycloak/keycloak:18.0.2 start-dev +``` + +Keycloak provides an easy-to-use web UI to help the administrator manage all resources, such as clients, roles, and users. + +Navigate to `http://localhost:8080` in browser to access the Keycloak web page, then click __Administration Console__: + +![web-ui](https://static.api7.ai/uploads/2023/03/30/ItcwYPIx_web-ui.png) + +Enter the administrator’s username `quickstart-admin` and password `quickstart-admin-pass` and sign in: + +![admin-signin](https://static.api7.ai/uploads/2023/03/30/6W3pjzE1_admin-signin.png) + +You need to maintain the login status to configure Keycloak during the following steps. + +### Create a Realm + +_Realms_ in Keycloak are workspaces to manage resources such as users, credentials, and roles. The resources in different realms are isolated from each other. You need to create a realm named `quickstart-realm` for APISIX. + +In the left menu, hover over **Master**, and select __Add realm__ in the dropdown: + +![create-realm](https://static.api7.ai/uploads/2023/03/30/S1Xvqliv_create-realm.png) + +Enter the realm name `quickstart-realm` and click __Create__ to create it: + +![add-realm](https://static.api7.ai/uploads/2023/03/30/jwb7QU8k_add-realm.png) + +### Create a Client + +_Clients_ in Keycloak are entities that request Keycloak to authenticate a user. More often, clients are applications that want to use Keycloak to secure themselves and provide a single sign-on solution. APISIX is equivalent to a client that is responsible for initiating authentication requests to Keycloak, so you need to create its corresponding client named `apisix-quickstart-client`. + +Click __Clients__ > __Create__ to open the __Add Client__ page: + +![create-client](https://static.api7.ai/uploads/2023/03/30/qLom0axN_create-client.png) + +Enter __Client ID__ as `apisix-quickstart-client`, then select __Client Protocol__ as `openid-connect` and __Save__: + +![add-client](https://static.api7.ai/uploads/2023/03/30/X5on2r7x_add-client.png) + +The client `apisix-quickstart-client` is created. After redirecting to the detailed page, select `confidential` as the __Access Type__: + +![config-client](https://static.api7.ai/uploads/2023/03/30/v70c8y9F_config-client.png) + +When the user login is successful during the SSO, Keycloak will carry the state and code to redirect the client to the addresses in __Valid Redirect URIs__. To simplify the operation, enter wildcard `*` to consider any URI valid: + +![client-redirect](https://static.api7.ai/uploads/2023/03/30/xLxcyVkn_client-redirect.png) + +If you are implementing the [authorization code grant with PKCE](#implement-authorization-code-grant), configure the PKCE challenge method in the client's advanced settings: + +
+PKCE keycloak configuration +
+ +If you are implementing [client credentials grant](#implement-client-credentials-grant), enable service accounts for the client: + +![enable-service-account](https://static.api7.ai/uploads/2023/12/29/h1uNtghd_sa.png) + +Select __Save__ to apply custom configurations. + +### Create a User + +Users in Keycloak are entities that are able to log into the system. They can have attributes associated with themselves, such as username, email, and address. + +If you are only implementing [client credentials grant](#implement-client-credentials-grant), you can [skip this section](#obtain-the-oidc-configuration). + +Click __Users__ > __Add user__ to open the __Add user__ page: + +![create-user](https://static.api7.ai/uploads/2023/03/30/onQEp23L_create-user.png) + +Enter the __Username__ as `quickstart-user` and select __Save__: + +![add-user](https://static.api7.ai/uploads/2023/03/30/EKhuhgML_add-user.png) + +Click on __Credentials__, then set the __Password__ as `quickstart-user-pass`. Switch __Temporary__ to `OFF` to turn off the restriction, so that you need not to change password the first time you log in: + +![user-pass](https://static.api7.ai/uploads/2023/03/30/rQKEAEnh_user-pass.png) + +## Obtain the OIDC Configuration + +In this section, you will obtain the key OIDC configuration from Keycloak and define them as shell variables. Steps after this section will use these variables to configure the OIDC by shell commands. + +:::info + +Open a separate terminal to follow the steps and define related shell variables. Then steps after this section could use the defined variables directly. + +::: + +### Get Discovery Endpoint + +Click __Realm Settings__, then right click __OpenID Endpoints Configuration__ and copy the link. + +![get-discovery](https://static.api7.ai/uploads/2023/03/30/526lbJbg_get-discovery.png) + +The link should be the same as the following: + +```text +http://localhost:8080/realms/quickstart-realm/.well-known/openid-configuration +``` + +Configuration values exposed with this endpoint are required during OIDC authentication. Update the address with your host IP and save to environment variables: + +```shell +export KEYCLOAK_IP=192.168.42.145 # replace with your host IP +export OIDC_DISCOVERY=http://${KEYCLOAK_IP}:8080/realms/quickstart-realm/.well-known/openid-configuration +``` + +### Get Client ID and Secret + +Click on __Clients__ > `apisix-quickstart-client` > __Credentials__, and copy the client secret from __Secret__: + +![client-ID](https://static.api7.ai/uploads/2023/03/30/MwYmU20v_client-id.png) + +![client-secret](https://static.api7.ai/uploads/2023/03/30/f9iOG8aN_client-secret.png) + +Save the OIDC client ID and secret to environment variables: + +```shell +export OIDC_CLIENT_ID=apisix-quickstart-client +export OIDC_CLIENT_SECRET=bSaIN3MV1YynmtXvU8lKkfeY0iwpr9cH # replace with your value +``` + +## Implement Authorization Code Grant + +The authorization code grant is used by web and mobile applications. The flow starts by authorization server displaying a login page in browser where users could key in their credentials. During the process, a short-lived authorization code is exchanged for an access token, which APISIX stores in browser session cookies and will be sent with every request visiting the upstream resource server. + +To implement authorization code grant, create a Route with `openid-connect` Plugin as such: + +```shell +curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d ' +{ + "id": "auth-with-oidc", + "uri":"/anything/*", + "plugins": { + "openid-connect": { + "bearer_only": false, + "client_id": "'"$OIDC_CLIENT_ID"'", + "client_secret": "'"$OIDC_CLIENT_SECRET"'", + "discovery": "'"$OIDC_DISCOVERY"'", + "scope": "openid profile", + "redirect_uri": "http://localhost:9080/anything/callback" + } + }, + "upstream":{ + "type":"roundrobin", + "nodes":{ + "httpbin.org:80":1 + } + } +}' +``` + +Alternatively, if you would like to implement authorization code grant with PKCE, create a Route with `openid-connect` Plugin as such: + +```shell +curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d ' +{ + "id": "auth-with-oidc", + "uri":"/anything/*", + "plugins": { + "openid-connect": { + "bearer_only": false, + "use_pkce": true, + "client_id": "'"$OIDC_CLIENT_ID"'", + "client_secret": "'"$OIDC_CLIENT_SECRET"'", + "discovery": "'"$OIDC_DISCOVERY"'", + "scope": "openid profile", + "redirect_uri": "http://localhost:9080/anything/callback" + } + }, + "upstream":{ + "type":"roundrobin", + "nodes":{ + "httpbin.org:80":1 + } + } +}' +``` + +### Verify with Valid Credentials + +Navigate to `http://127.0.0.1:9080/anything/test` in browser. The request will be redirected to a login page: + +![test-sign-on](https://static.api7.ai/uploads/2023/03/30/i38u1x9a_validate-sign.png) + +Log in with the correct username `quickstart-user` and password `quickstart-user-pass`. If successful, the request will be forwarded to `httpbin.org` and you should see a response similar to the following: + +```json +{ + "args": {}, + "data": "", + "files": {}, + "form": {}, + "headers": { + "Accept": "text/html..." + ... + }, + "json": null, + "method": "GET", + "origin": "127.0.0.1, 59.71.244.81", + "url": "http://127.0.0.1/anything/test" +} +``` + +### Verify with Invalid Credentials + +Sign in with the wrong credentials. You should see an authentication failure: + +![test-sign-failed](https://static.api7.ai/uploads/2023/03/31/YOuSYX1r_validate-sign-failed.png) + +## Implement Client Credentials Grant + +In client credentials grant, clients obtain access tokens without any users involved. It is typically used in machine-to-machine (M2M) communications. + +To implement client credentials grant, create a Route with `openid-connect` Plugin to use the JWKS endpoint of the identity provider to verify the token. The endpoint would be obtained from the discovery document. + +```shell +curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d ' +{ + "id": "auth-with-oidc", + "uri":"/anything/*", + "plugins": { + "openid-connect": { + "use_jwks": true, + "client_id": "'"$OIDC_CLIENT_ID"'", + "client_secret": "'"$OIDC_CLIENT_SECRET"'", + "discovery": "'"$OIDC_DISCOVERY"'", + "scope": "openid profile", + "redirect_uri": "http://localhost:9080/anything/callback" + } + }, + "upstream":{ + "type":"roundrobin", + "nodes":{ + "httpbin.org:80":1 + } + } +}' +``` + +Alternatively, if you would like to use the introspection endpoint to verify the token, create the Route as such: + +```shell +curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d ' +{ + "id": "auth-with-oidc", + "uri":"/anything/*", + "plugins": { + "openid-connect": { + "bearer_only": true, + "client_id": "'"$OIDC_CLIENT_ID"'", + "client_secret": "'"$OIDC_CLIENT_SECRET"'", + "discovery": "'"$OIDC_DISCOVERY"'", + "scope": "openid profile", + "redirect_uri": "http://localhost:9080/anything/callback" + } + }, + "upstream":{ + "type":"roundrobin", + "nodes":{ + "httpbin.org:80":1 + } + } +}' +``` + +The introspection endpoint will be obtained from the discovery document. + +### Verify With Valid Access Token + +Obtain an access token for the Keycloak server at the [token endpoint](https://www.keycloak.org/docs/latest/securing_apps/#token-endpoint): + +```shell +curl -i "http://$KEYCLOAK_IP:8080/realms/quickstart-realm/protocol/openid-connect/token" -X POST \ + -d 'grant_type=client_credentials' \ + -d 'client_id='$OIDC_CLIENT_ID'' \ + -d 'client_secret='$OIDC_CLIENT_SECRET'' +``` + +The expected response is similar to the following: + +```text +{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJoT3ludlBPY2d6Y3VWWnYtTU42bXZKMUczb0dOX2d6MFo3WFl6S2FSa1NBIn0.eyJleHAiOjE3MDM4MjU1NjQsImlhdCI6MTcwMzgyNTI2NCwianRpIjoiMWQ4NWE4N2UtZDFhMC00NThmLThiMTItNGZiYWM2ODA5YmYwIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMS44Mzo4MDgwL3JlYWxtcy9xdWlja3N0YXJ0LXJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjE1OGUzOWFlLTk0YjAtNDI3Zi04ZGU3LTU3MTRhYWYwOGYzOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1xdWlja3N0YXJ0LXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50SG9zdCI6IjE3Mi4xNy4wLjEiLCJjbGllbnRJZCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1hcGlzaXgtcXVpY2tzdGFydC1jbGllbnQiLCJjbGllbnRBZGRyZXNzIjoiMTcyLjE3LjAuMSJ9.TltzSXqrJuVID7aGrb35jn-oc07U_-jugSn-3jKz4A44LwtAsME_8b3qkmR4boMOIht_5pF6bnnp70MFAlg6JKu4_yIQDxF_GAHjnZXEO8OCKhtIKwXm2w-hnnJVIhIdGkIVkbPP0HfILuar_m0hpa53VpPBGYR-OS4pyh0KTUs8MB22xAEqyz9zjCm6SX9vXCqgeVkSpRW2E8NaGEbAdY25uY-ZC4dI_pON87Ey5e8GdD6HQLXQlGIOdCDi3N7k0HDoD9TZRv2bMRPfy4zVYm1ZlClIuF79A-ZBwr0c-XYuq7t6EY0gPGEXB-s0SaKlrIU5S9JBeVXRzYvqAih41g","expires_in":300,"refresh_expires_in":0,"token_type":"Bearer","not-before-policy":0,"scope":"email profile"} +``` + +Save the access token to an environment variable: + +```shell +# replace with your access token +export ACCESS_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJoT3ludlBPY2d6Y3VWWnYtTU42bXZKMUczb0dOX2d6MFo3WFl6S2FSa1NBIn0.eyJleHAiOjE3MDM4MjU1NjQsImlhdCI6MTcwMzgyNTI2NCwianRpIjoiMWQ4NWE4N2UtZDFhMC00NThmLThiMTItNGZiYWM2ODA5YmYwIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMS44Mzo4MDgwL3JlYWxtcy9xdWlja3N0YXJ0LXJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjE1OGUzOWFlLTk0YjAtNDI3Zi04ZGU3LTU3MTRhYWYwOGYzOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1xdWlja3N0YXJ0LXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50SG9zdCI6IjE3Mi4xNy4wLjEiLCJjbGllbnRJZCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1hcGlzaXgtcXVpY2tzdGFydC1jbGllbnQiLCJjbGllbnRBZGRyZXNzIjoiMTcyLjE3LjAuMSJ9.TltzSXqrJuVID7aGrb35jn-oc07U_-jugSn-3jKz4A44LwtAsME_8b3qkmR4boMOIht_5pF6bnnp70MFAlg6JKu4_yIQDxF_GAHjnZXEO8OCKhtIKwXm2w-hnnJVIhIdGkIVkbPP0HfILuar_m0hpa53VpPBGYR-OS4pyh0KTUs8MB22xAEqyz9zjCm6SX9vXCqgeVkSpRW2E8NaGEbAdY25uY-ZC4dI_pON87Ey5e8GdD6HQLXQlGIOdCDi3N7k0HDoD9TZRv2bMRPfy4zVYm1ZlClIuF79A-ZBwr0c-XYuq7t6EY0gPGEXB-s0SaKlrIU5S9JBeVXRzYvqAih41g" +``` + +Send a request to the route with the valid access token: + +```shell +curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer $ACCESS_TOKEN" +``` + +An `HTTP/1.1 200 OK` response verifies that the request to the upstream resource was authorized. + +### Verify With Invalid Access Token + +Send a request to the Route with invalid access token: + +```shell +curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer invalid-access-token" +``` + +An `HTTP/1.1 401 Unauthorized` response verifies that the OIDC Plugin rejects requests with invalid access token. + +### Verify without Access Token + +Send a request to the Route without access token: + +```shell +curl -i "http://127.0.0.1:9080/anything/test" +``` + +An `HTTP/1.1 401 Unauthorized` response verifies that the OIDC Plugin rejects requests without access token. + +## Implement Password Grant + +Password grant is a legacy approach to exchange user credentials for an access token. + +To implement password grant, create a Route with `openid-connect` Plugin to use the JWKS endpoint of the identity provider to verify the token. The endpoint would be obtained from the discovery document. + +```shell +curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d ' +{ + "id": "auth-with-oidc", + "uri":"/anything/*", + "plugins": { + "openid-connect": { + "use_jwks": true, + "client_id": "'"$OIDC_CLIENT_ID"'", + "client_secret": "'"$OIDC_CLIENT_SECRET"'", + "discovery": "'"$OIDC_DISCOVERY"'", + "scope": "openid profile", + "redirect_uri": "http://localhost:9080/anything/callback" + } + }, + "upstream":{ + "type":"roundrobin", + "nodes":{ + "httpbin.org:80":1 + } + } +}' +``` + +### Verify With Valid Access Token + +Obtain an access token for the Keycloak server at the [token endpoint](https://www.keycloak.org/docs/latest/securing_apps/#token-endpoint): + +```shell +OIDC_USER=quickstart-user +OIDC_PASSWORD=quickstart-user-pass +curl -i "http://$KEYCLOAK_IP:8080/realms/quickstart-realm/protocol/openid-connect/token" -X POST \ + -d 'grant_type=password' \ + -d 'client_id='$OIDC_CLIENT_ID'' \ + -d 'client_secret='$OIDC_CLIENT_SECRET'' \ + -d 'username='$OIDC_USER'' \ + -d 'password='$OIDC_PASSWORD'' +``` + +The expected response is similar to the following: + +```text +{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ6U3FFaXN6VlpuYi1sRWMzZkp0UHNpU1ZZcGs4RGN3dXI1Mkx5V05aQTR3In0.eyJleHAiOjE2ODAxNjA5NjgsImlhdCI6MTY4MDE2MDY2OCwianRpIjoiMzQ5MTc4YjQtYmExZC00ZWZjLWFlYTUtZGY2MzJiMDJhNWY5IiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguNDIuMTQ1OjgwODAvcmVhbG1zL3F1aWNrc3RhcnQtcmVhbG0iLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTg4MTVjM2EtNmQwNy00YTY2LWJjZjItYWQ5NjdmMmIwMTFmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYXBpc2l4LXF1aWNrc3RhcnQtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6ImIxNmIyNjJlLTEwNTYtNDUxNS1hNDU1LWYyNWUwNzdjY2I3NiIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1xdWlja3N0YXJ0LXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6ImIxNmIyNjJlLTEwNTYtNDUxNS1hNDU1LWYyNWUwNzdjY2I3NiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoicXVpY2tzdGFydC11c2VyIn0.uD_7zfZv5182aLXu9-YBzBDK0nr2mE4FWb_4saTog2JTqFTPZZa99Gm8AIDJx2ZUcZ_ElkATqNUZ4OpWmL2Se5NecMw3slJReewjD6xgpZ3-WvQuTGpoHdW5wN9-Rjy8ungilrnAsnDA3tzctsxm2w6i9KISxvZrzn5Rbk-GN6fxH01VC5eekkPUQJcJgwuJiEiu70SjGnm21xDN4VGkNRC6jrURoclv3j6AeOqDDIV95kA_MTfBswDFMCr2PQlj5U0RTndZqgSoxwFklpjGV09Azp_jnU7L32_Sq-8coZd0nj5mSdbkJLJ8ZDQDV_PP3HjCP7EHdy4P6TyZ7oGvjw","expires_in":300,"refresh_expires_in":1800,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0YjFiNTQ3Yi0zZmZjLTQ5YzQtYjE2Ni03YjdhNzIxMjk1ODcifQ.eyJleHAiOjE2ODAxNjI0NjgsImlhdCI6MTY4MDE2MDY2OCwianRpIjoiYzRjNjNlMTEtZTdlZS00ZmEzLWJlNGYtNDMyZWQ4ZmY5OTQwIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguNDIuMTQ1OjgwODAvcmVhbG1zL3F1aWNrc3RhcnQtcmVhbG0iLCJhdWQiOiJodHRwOi8vMTkyLjE2OC40Mi4xNDU6ODA4MC9yZWFsbXMvcXVpY2tzdGFydC1yZWFsbSIsInN1YiI6IjE4ODE1YzNhLTZkMDctNGE2Ni1iY2YyLWFkOTY3ZjJiMDExZiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJhcGlzaXgtcXVpY2tzdGFydC1jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiYjE2YjI2MmUtMTA1Ni00NTE1LWE0NTUtZjI1ZTA3N2NjYjc2Iiwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiYjE2YjI2MmUtMTA1Ni00NTE1LWE0NTUtZjI1ZTA3N2NjYjc2In0.8xYP4bhDg1U9B5cTaEVD7B4oxNp8wwAYEynUne_Jm78","token_type":"Bearer","not-before-policy":0,"session_state":"b16b262e-1056-4515-a455-f25e077ccb76","scope":"profile email"} +``` + +Save the access token and refresh token to environment variables. The refresh token will be used in the [refresh token step](#refresh-token). + +```shell +# replace with your access token +export ACCESS_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ6U3FFaXN6VlpuYi1sRWMzZkp0UHNpU1ZZcGs4RGN3dXI1Mkx5V05aQTR3In0.eyJleHAiOjE2ODAxNjA5NjgsImlhdCI6MTY4MDE2MDY2OCwianRpIjoiMzQ5MTc4YjQtYmExZC00ZWZjLWFlYTUtZGY2MzJiMDJhNWY5IiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguNDIuMTQ1OjgwODAvcmVhbG1zL3F1aWNrc3RhcnQtcmVhbG0iLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTg4MTVjM2EtNmQwNy00YTY2LWJjZjItYWQ5NjdmMmIwMTFmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYXBpc2l4LXF1aWNrc3RhcnQtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6ImIxNmIyNjJlLTEwNTYtNDUxNS1hNDU1LWYyNWUwNzdjY2I3NiIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1xdWlja3N0YXJ0LXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6ImIxNmIyNjJlLTEwNTYtNDUxNS1hNDU1LWYyNWUwNzdjY2I3NiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoicXVpY2tzdGFydC11c2VyIn0.uD_7zfZv5182aLXu9-YBzBDK0nr2mE4FWb_4saTog2JTqFTPZZa99Gm8AIDJx2ZUcZ_ElkATqNUZ4OpWmL2Se5NecMw3slJReewjD6xgpZ3-WvQuTGpoHdW5wN9-Rjy8ungilrnAsnDA3tzctsxm2w6i9KISxvZrzn5Rbk-GN6fxH01VC5eekkPUQJcJgwuJiEiu70SjGnm21xDN4VGkNRC6jrURoclv3j6AeOqDDIV95kA_MTfBswDFMCr2PQlj5U0RTndZqgSoxwFklpjGV09Azp_jnU7L32_Sq-8coZd0nj5mSdbkJLJ8ZDQDV_PP3HjCP7EHdy4P6TyZ7oGvjw" +export REFRESH_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0YjFiNTQ3Yi0zZmZjLTQ5YzQtYjE2Ni03YjdhNzIxMjk1ODcifQ.eyJleHAiOjE2ODAxNjI0NjgsImlhdCI6MTY4MDE2MDY2OCwianRpIjoiYzRjNjNlMTEtZTdlZS00ZmEzLWJlNGYtNDMyZWQ4ZmY5OTQwIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguNDIuMTQ1OjgwODAvcmVhbG1zL3F1aWNrc3RhcnQtcmVhbG0iLCJhdWQiOiJodHRwOi8vMTkyLjE2OC40Mi4xNDU6ODA4MC9yZWFsbXMvcXVpY2tzdGFydC1yZWFsbSIsInN1YiI6IjE4ODE1YzNhLTZkMDctNGE2Ni1iY2YyLWFkOTY3ZjJiMDExZiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJhcGlzaXgtcXVpY2tzdGFydC1jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiYjE2YjI2MmUtMTA1Ni00NTE1LWE0NTUtZjI1ZTA3N2NjYjc2Iiwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiYjE2YjI2MmUtMTA1Ni00NTE1LWE0NTUtZjI1ZTA3N2NjYjc2In0.8xYP4bhDg1U9B5cTaEVD7B4oxNp8wwAYEynUne_Jm78" +``` + +Send a request to the route with the valid access token: + +```shell +curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer $ACCESS_TOKEN" +``` + +An `HTTP/1.1 200 OK` response verifies that the request to the upstream resource was authorized. + +### Verify With Invalid Access Token + +Send a request to the Route with invalid access token: + +```shell +curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer invalid-access-token" +``` + +An `HTTP/1.1 401 Unauthorized` response verifies that the OIDC Plugin rejects requests with invalid access token. + +### Verify without Access Token + +Send a request to the Route without access token: + +```shell +curl -i "http://127.0.0.1:9080/anything/test" +``` + +An `HTTP/1.1 401 Unauthorized` response verifies that the OIDC Plugin rejects requests without access token. + +### Refresh Token + +To refresh the access token, send a request to the Keycloak token endpoint as such: + +```shell +curl -i "http://$KEYCLOAK_IP:8080/realms/quickstart-realm/protocol/openid-connect/token" -X POST \ + -d 'grant_type=refresh_token' \ + -d 'client_id='$OIDC_CLIENT_ID'' \ + -d 'client_secret='$OIDC_CLIENT_SECRET'' \ + -d 'refresh_token='$REFRESH_TOKEN'' +``` + +You should see a response similar to the following, with the new access token and refresh token, which you can use for subsequent requests and token refreshes: + +```text +{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJTdnVwLXlPMHhDdTJBVi1za2pCZ0h6SHZNaG1mcDVDQWc0NHpYb2QxVTlNIn0.eyJleHAiOjE3MzAyNzQ3NDUsImlhdCI6MTczMDI3NDQ0NSwianRpIjoiMjk2Mjk5MWUtM2ExOC00YWFiLWE0NzAtODgxNWEzNjZjZmM4IiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMTUyLjU6ODA4MC9yZWFsbXMvcXVpY2tzdGFydC1yZWFsbSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiI2ZWI0ZTg0Yy00NmJmLTRkYzUtOTNkMC01YWM5YzE5MWU0OTciLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhcGlzaXgtcXVpY2tzdGFydC1jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiNTU2ZTQyYjktMjE2Yi00NTEyLWE5ZjAtNzE3ZTAyYTQ4MjZhIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLXF1aWNrc3RhcnQtcmVhbG0iLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiNTU2ZTQyYjktMjE2Yi00NTEyLWE5ZjAtNzE3ZTAyYTQ4MjZhIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJxdWlja3N0YXJ0LXVzZXIifQ.KLqn1LQdazoPBqLLR856C35XpqbMO9I7WFt3KrDxZF1N8vwv4AvZYWI_2rsbdjCakh9JmPgyYRgEGufYLiDBsqy9CrMVejAIJPYsJIonIXBCp5Ysu92ODJuqtTKuuJ6K7dam7fisBFfCBbVvGspnZ3p0caedpOaF_kSd-F8ARHKVsmkuX3_ucDrP3UctjEXHezefTY4YHjNMB9wuMDPXX2vXt2BsOasnznsIHHHX-ZH8JY6eEfWPtfx0qAED6lVZICT6Rqj_j5-Cf9ogzFtLyy_XvtG9BbHME2B8AXYpxdzqxOxmVVbZdrB8elfmFjs1R3vUn2r3xA9hO_znZo_IoQ","expires_in":300,"refresh_expires_in":1800,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIwYWYwZTAwYy0xMThjLTRkNDktYmIwMS1iMDIwNDE3MmFjMzIifQ.eyJleHAiOjE3MzAyNzYyNDUsImlhdCI6MTczMDI3NDQ0NSwianRpIjoiZGQyZTJmYTktN2Y3Zi00MjM5LWEwODAtNWQyZDFiZTdjNzk4IiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMTUyLjU6ODA4MC9yZWFsbXMvcXVpY2tzdGFydC1yZWFsbSIsImF1ZCI6Imh0dHA6Ly8xOTIuMTY4LjE1Mi41OjgwODAvcmVhbG1zL3F1aWNrc3RhcnQtcmVhbG0iLCJzdWIiOiI2ZWI0ZTg0Yy00NmJmLTRkYzUtOTNkMC01YWM5YzE5MWU0OTciLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiYXBpc2l4LXF1aWNrc3RhcnQtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjU1NmU0MmI5LTIxNmItNDUxMi1hOWYwLTcxN2UwMmE0ODI2YSIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6IjU1NmU0MmI5LTIxNmItNDUxMi1hOWYwLTcxN2UwMmE0ODI2YSJ9.Uad4BVuojHfyxqedFT5BHliWjIqVDbjM-Xeme0G2AAg","token_type":"Bearer","not-before-policy":0,"session_state":"556e42b9-216b-4512-a9f0-717e02a4826a","scope":"email profile"} +``` diff --git a/docs/zh/latest/plugins/openid-connect.md b/docs/zh/latest/plugins/openid-connect.md index 5f99f0e1be26..58fbb86d0bed 100644 --- a/docs/zh/latest/plugins/openid-connect.md +++ b/docs/zh/latest/plugins/openid-connect.md @@ -5,7 +5,7 @@ keywords: - API 网关 - OpenID Connect - OIDC -description: OpenID Connect(OIDC)是基于 OAuth 2.0 的身份认证协议,APISIX 可以与支持该协议的身份认证服务对接,如 Okta、Keycloak、Ory Hydra、Authing 等,实现对客户端请求的身份认证。 +description: openid-connect 插件支持与 OpenID Connect (OIDC) 身份提供商集成,例如 Keycloak、Auth0、Microsoft Entra ID、Google、Okta 等。它允许 APISIX 对客户端进行身份验证并从身份提供商处获取其信息,然后允许或拒绝其访问上游受保护资源。 --- + + + + ## 描述 -[OpenID Connect](https://openid.net/connect/)(OIDC)是基于 OAuth 2.0 的身份认证协议,APISIX 可以与支持该协议的身份认证服务对接,如 Okta、Keycloak、Ory Hydra、Authing 等,实现对客户端请求的身份认证。 +`openid-connect` 插件支持与 [OpenID Connect (OIDC)](https://openid.net/connect/) 身份提供商集成,例如 Keycloak、Auth0、Microsoft Entra ID、Google、Okta 等。它允许 APISIX 对客户端进行身份验证,并从身份提供商处获取其信息,然后允许或拒绝其访问上游受保护资源。 ## 属性 @@ -37,199 +41,156 @@ description: OpenID Connect(OIDC)是基于 OAuth 2.0 的身份认证协议 | ------------------------------------ | ------- | ------ | --------------------- | ------------- | ------------------------------------------------------------------------------------------------ | | client_id | string | 是 | | | OAuth 客户端 ID。 | | client_secret | string | 是 | | | OAuth 客户端 secret。 | -| discovery | string | 是 | | | 身份认证服务暴露的服务发现端点。 | -| scope | string | 否 | "openid" | | OIDC 范围对应于应返回的有关经过身份验证的用户的信息,也称为 [claims](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims)。默认值是`openid`,这是 OIDC 返回唯一标识经过身份验证的用户的 `sub` 声明所需的范围。可以附加其他范围并用空格分隔,例如 `openid email profile`。 | -| realm | string | 否 | "apisix" | | bearer token 无效时 [`WWW-Authenticate` 响应头](https://www.rfc-editor.org/rfc/rfc6750#section-3)中会伴随着的 `realm` 讯息。 | -| bearer_only | boolean | 否 | false | | 当设置为 `true` 时,将仅检查请求头中的令牌(Token)。 | -| logout_path | string | 否 | "/logout" | | 登出路径。 | -| post_logout_redirect_uri | string | 否 | | | 调用登出接口后想要跳转的 URL。如果 OIDC 的服务发现端点没有提供 [`end_session_endpoint`](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) ,插件内部会使用 [`redirect_after_logout_uri`](https://github.com/zmartzone/lua-resty-openidc) 进行重定向,否则使用 [`post_logout_redirect_uri`](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) 进行重定向。 | -| redirect_uri | string | 否 | | | 身份提供者重定向返回的 URI。如果缺失,则 APISIX 将在当前 URI 之后追加 `.apisix/redirect` 作为默认的 `redirect_uri`。注意,OP 也需要适当配置以允许这种形式的 `redirect_uri`。 | -| timeout | integer | 否 | 3 | [1,...] | 请求超时时间,单位为秒 | -| ssl_verify | boolean | 否 | false | [true, false] | 当设置为 `true` 时,验证身份提供者的 SSL 证书。 | -| introspection_endpoint | string | 否 | | | 用于内省访问令牌的身份提供者的令牌内省端点的 URL。如果未设置,则使用发现文档中提供的内省端点[作为后备](https://github.com/zmartzone/lua-resty-openidc/commit/cdaf824996d2b499de4c72852c91733872137c9c)。 | -| introspection_endpoint_auth_method | string | 否 | "client_secret_basic" | | 令牌内省端点的身份验证方法。该值应是 `introspection_endpoint_auth_methods_supported` [授权服务器元数据](https://www.rfc-editor.org/rfc/rfc8414.html)中指定的身份验证方法之一,如发现文档中所示,例如 `client_secret_basic`, `client_secret_post`, `private_key_jwt`,或 `client_secret_jwt`。 | -| token_endpoint_auth_method | string | 否 | | | 令牌端点的身份验证方法。该值应是 `token_endpoint_auth_methods_supported` [授权服务器元数据](https://www.rfc-editor.org/rfc/rfc8414.html)中指定的身份验证方法之一,如发现文档中所示,例如 `client_secret_basic`, `client_secret_post`, `private_key_jwt`,或 `client_secret_jwt`。如果不支持配置的方法,则回退到`token_endpoint_auth_methods_supported` 数组中的第一个方法。 | -| public_key | string | 否 | | | 验证令牌的公钥。 | -| use_jwks | boolean | 否 | false | | 当设置为 `true` 时,则会使用身份认证服务器的 JWKS 端点来验证令牌。 | -| use_pkce | boolean | 否 | false | [true, false] | 当设置为 `true` 时,则使用 PKCE(Proof Key for Code Exchange)。 | -| token_signing_alg_values_expected | string | 否 | | | 用于对令牌进行签名的算法。 | -| set_access_token_header | boolean | 否 | true | [true, false] | 在请求头设置访问令牌。默认使用请求头参数 `X-Access-Token`。 | -| access_token_in_authorization_header | boolean | 否 | false | [true, false] | 当设置为 `true` 以及 `set_access_token_header` 也设置为 `true`时,将访问令牌设置在请求头参数 `Authorization`。 | -| set_id_token_header | boolean | 否 | true | [true, false] | 是否将 ID 令牌设置到请求头参数 `X-ID-Token`。 | -| set_userinfo_header | boolean | 否 | true | [true, false] | 是否将用户信息对象设置到请求头参数 `X-Userinfo`。 | -| set_refresh_token_header | boolean | 否 | false | | 当设置为 `true` 并且刷新令牌可用时,则会将该属性设置在`X-Refresh-Token`请求头中。 | -| session | object | 否 | | | 当设置 bearer_only 为 false 时,openid-connect 插件将使用 Authorization Code 在 IDP 上进行认证,因此你必须设置 session 相关设置。 | -| session.secret | string | 是 | 自动生成 | 16 个以上字符 | 用于 session 加密和 HMAC 计算的密钥。 | -| session.cookie | object | False | | | | -| session.cookie.lifetime | integer | False | 3600 | | 用于设置 cookie 的生命周期,以秒为单位。 | -| unauth_action | string | False | "auth" | ["auth","deny","pass"] | 指定未经身份验证的请求的响应类型。 `auth` 重定向到身份提供者,`deny` 导致 401 响应,`pass` 将允许请求而无需身份验证。 | -| proxy_opts | object | 否 | | | OpenID 服务器前面的 HTTP 代理服务器。 | -| proxy_opts | object | 否 | | | 用来访问身份认证服务器的代理服务器。 | -| proxy_opts.http_proxy | string | 否 | | http://proxy-server:port | HTTP 代理服务器地址。 | -| proxy_opts.https_proxy | string | 否 | | http://proxy-server:port | HTTPS 代理服务器地址。 | -| proxy_opts.http_proxy_authorization | string | 否 | | Basic [base64 username:password] | 与 `http_proxy` 一起使用的默认 `Proxy-Authorization` 标头值。可以使用自定义 `Proxy-Authorization` 请求标头覆盖。 | -| proxy_opts.https_proxy_authorization | string | 否 | | Basic [base64 username:password] | 与 `https_proxy` 一起使用的默认 `Proxy-Authorization` 标头值。无法使用自定义 `Proxy-Authorization` 请求标头覆盖,因为使用 HTTPS 时,授权在连接时完成。 | -| proxy_opts.no_proxy | string | 否 | | | 不应被代理的主机的逗号分隔列表。 | -| authorization_params | object | 否 | | | 在请求中发送到授权端点的附加参数 | -| client_rsa_private_key | string | 否 | | | 用于签署 JWT 的客户端 RSA 私钥。 | -| client_rsa_private_key_id | string | 否 | | | 用于计算签名 JWT 的客户端 RSA 私钥 ID。 | -| client_jwt_assertion_expires_in | integer | 否 | 60 | | 签名 JWT 的生存期,以秒为单位。 | -| renew_access_token_on_expiry | boolean | 否 | true | | 如果为 true,在访问令牌过期或存在刷新令牌时,尝试静默更新访问令牌。如果令牌无法更新,则重定向用户进行重新认证。 | -| access_token_expires_in | integer | 否 | | | 访问令牌的生命周期,以秒为单位,如果令牌终端响应中不存在 `expires_in` 属性。 | -| refresh_session_interval | integer | 否 | 900 | | 刷新用户 ID 令牌而无需重新进行身份验证的时间间隔,以秒为单位。若未设置,则不会检查网关向客户端签发的 ID 令牌(如浏览器中的 session)过期时间。如果设置为 900,意味着在 900 秒后刷新用户的 ID 令牌(如浏览器中的 session),而无需重新进行身份验证。 | -| iat_slack | integer | 否 | 120 | | 与 ID 令牌中的 `iat` 声明的时钟偏差容忍度,以秒为单位。 | -| accept_none_alg | boolean | 否 | false | | 如果 OpenID 提供程序不对其 ID 令牌进行签名将其设置为 true。 | -| accept_unsupported_alg | boolean | 否 | true | | 如果为 true,忽略 ID 令牌签名以接受不支持的签名算法。 | -| access_token_expires_leeway | integer | 否 | 0 | | 访问令牌续订的过期宽限期,以秒为单位。当设置为大于 0 的值时,令牌续订将在令牌到期之前的一段时间内进行。这样可以避免在到达资源服务器时令牌刚好过期时出现错误。 | -| force_reauthorize | boolean | 否 | false | | 如果为 true,即使已缓存令牌,也执行授权流程。 | -| use_nonce | boolean | 否 | false | | 如果为 true,启用授权请求中的 nonce 参数。| -| revoke_tokens_on_logout | boolean | 否 | false | | 如果为 true,通知授权服务器不再需要先前获取的刷新令牌或访问令牌,发送到吊销端点。| -| jwk_expires_in | integer | 否 | 86400 | | JWK 缓存的过期时间,以秒为单位。| -| jwt_verification_cache_ignore | boolean | 否 | false | | 如果为 true,请强制对持有者令牌进行重新验证,并忽略任何现有的缓存验证结果。 | -| cache_segment | string | 否 | | | 可选的缓存段的名称,用于区分和区分用于令牌内省或 JWT 验证的缓存。 | -| introspection_interval | integer | 否 | 0 | | 以秒为单位的缓存和内省访问令牌的 TTL。 | -| introspection_expiry_claim | string | 否 | | | 过期声明的名称,用于控制缓存和内省访问令牌的 TTL。 | -| introspection_addon_headers | string[] | 否 | | | `introspection_addon_headers` 是字符串列表,用于配置额外添加到内省 HTTP 请求中的请求头,如果配置的请求头不存在于源请求中,它将被忽略。| +| discovery | string | True | | | OpenID 提供商的知名发现文档的 URL,其中包含 OP API 端点列表。插件可以直接利用发现文档中的端点。您也可以单独配置这些端点,这优先于发现文档中提供的端点。 | +| scope | string | False | openid | | 与应返回的有关经过身份验证的用户的信息相对应的 OIDC 范围,也称为 [claim](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims)。这用于向用户授权适当的权限。默认值为 `openid`,这是 OIDC 返回唯一标识经过身份验证的用户的 `sub` 声明所需的范围。可以附加其他范围并用空格分隔,例如 `openid email profile`。 | +| required_scopes | array[string] | False | | | 访问令牌中必须存在的范围。当 `bearer_only` 为 `true` 时与自省端点结合使用。如果缺少任何必需的范围,插件将以 403 禁止错误拒绝请求。| +| realm | string | False | apisix | | 由于持有者令牌无效,[`WWW-Authenticate`](https://www.rfc-editor.org/rfc/rfc6750#section-3) 响应标头中的领域伴随 401 未经授权的请求。 | +| bearer_only | boolean | False | false | | 如果为 true,则严格要求在身份验证请求中使用持有者访问令牌。 | +| logout_path | string | False | /logout | | 激活注销的路径。 | +| post_logout_redirect_uri | string | False | | | `logout_path` 收到注销请求后将用户重定向到的 URL。| +| redirect_uri | string | False | | | 通过 OpenID 提供商进行身份验证后重定向到的 URI。请注意,重定向 URI 不应与请求 URI 相同,而应为请求 URI 的子路径。例如,如果路由的 `uri` 是 `/api/v1/*`,则 `redirect_uri` 可以配置为 `/api/v1/redirect`。如果未配置 `redirect_uri`,APISIX 将在请求 URI 后附加 `/.apisix/redirect` 以确定 `redirect_uri` 的值。| +| timeout | integer | False | 3 | [1,...] | 请求超时时间(秒)。| +| ssl_verify | boolean | False | false | | 如果为 true,则验证 OpenID 提供商的 SSL 证书。| +| introspection_endpoint | string | False | | |用于自检访问令牌的 OpenID 提供程序的 [令牌自检](https://datatracker.ietf.org/doc/html/rfc7662) 端点的 URL。如果未设置,则将使用众所周知的发现文档中提供的自检端点[作为后备](https://github.com/zmartzone/lua-resty-openidc/commit/cdaf824996d2b499de4c72852c91733872137c9c)。| +| introspection_endpoint_auth_method | string | False | client_secret_basic | | 令牌自检端点的身份验证方法。该值应为 `introspection_endpoint_auth_methods_supported` [授权服务器元数据](https://www.rfc-editor.org/rfc/rfc8414.html) 中指定的身份验证方法之一,如众所周知的发现文档中所示,例如 `client_secret_basic`、`client_secret_post`、`private_key_jwt` 和 `client_secret_jwt`。| +| token_endpoint_auth_method | string | False | client_secret_basic | | 令牌端点的身份验证方法。该值应为 `token_endpoint_auth_methods_supported` [授权服务器元数据](https://www.rfc-editor.org/rfc/rfc8414.html) 中指定的身份验证方法之一,如众所周知的发现文档中所示,例如 `client_secret_basic`、`client_secret_post`、`private_key_jwt` 和 `client_secret_jwt`。如果配置的方法不受支持,则回退到 `token_endpoint_auth_methods_supported` 数组中的第一个方法。| +| public_key | string | False | | | 用于验证 JWT 签名 id 的公钥使用非对称算法。提供此值来执行令牌验证将跳过客户端凭据流中的令牌自检。您可以以 `-----BEGIN PUBLIC KEY-----\\n……\\n-----END PUBLIC KEY-----` 格式传递公钥。| +| use_jwks | boolean | False | false | | 如果为 true 并且未设置 `public_key`,则使用 JWKS 验证 JWT 签名并跳过客户端凭据流中的令牌自检。JWKS 端点是从发现文档中解析出来的。| +| use_pkce | boolean | False | false | | 如果为 true,则使用 [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) 中定义的授权码流的代码交换证明密钥 (PKCE)。| +| token_signing_alg_values_expected | string | False | | | 用于签署 JWT 的算法,例如 `RS256`。 | +| set_access_token_header | boolean | False | true | | 如果为 true,则在请求标头中设置访问令牌。默认情况下,使用 `X-Access-Token` 标头。| +| access_token_in_authorization_header | boolean | False | false | | 如果为 true 并且 `set_access_token_header` 也为 true,则在 `Authorization` 标头中设置访问令牌。 | +| set_id_token_header | boolean | False | true | | 如果为 true 并且 ID 令牌可用,则在 `X-ID-Token` 请求标头中设置值。 | +| set_userinfo_header | boolean | False | true | | 如果为 true 并且用户信息数据可用,则在 `X-Userinfo` 请求标头中设置值。 | +| set_refresh_token_header | boolean | False | false | | 如果为 true 并且刷新令牌可用,则在 `X-Refresh-Token` 请求标头中设置值。 | +| session | object | False | | | 当 `bearer_only` 为 `false` 且插件使用 Authorization Code 流程时使用的 Session 配置。 | +| session.secret | string | True | | 16 个字符以上 | 当 `bearer_only` 为 `false` 时,用于 session 加密和 HMAC 运算的密钥,若未配置则自动生成并保存到 etcd。当在独立模式下使用 APISIX 时,etcd 不再是配置中心,需要配置 `secret`。 | +| session.cookie | object | False | | | Cookie 配置。 | +| session.cookie.lifetime | integer | False | 3600 | | Cookie 生存时间(秒)。| +| unauth_action | string | False | auth | ["auth","deny","pass"] | 未经身份验证的请求的操作。设置为 `auth` 时,重定向到 OpenID 提供程序的身份验证端点。设置为 `pass` 时,允许请求而无需身份验证。设置为 `deny` 时,返回 401 未经身份验证的响应,而不是启动授权代码授予流程。| +| proxy_opts | object | False | | | OpenID 提供程序背后的代理服务器的配置。| +| proxy_opts.http_proxy | string | False | | | HTTP 请求的代理服务器地址,例如 `http://:`。| +| proxy_opts.https_proxy | string | False | | | HTTPS 请求的代理服务器地址,例如 `http://:`。 | +| proxy_opts.http_proxy_authorization | string | False | | Basic [base64 用户名:密码] | 与 `http_proxy` 一起使用的默认 `Proxy-Authorization` 标头值。可以用自定义的 `Proxy-Authorization` 请求标头覆盖。 | +| proxy_opts.https_proxy_authorization | string | False | | Basic [base64 用户名:密码] | 与 `https_proxy` 一起使用的默认 `Proxy-Authorization` 标头值。不能用自定义的 `Proxy-Authorization` 请求标头覆盖,因为使用 HTTPS 时,授权在连接时完成。 | +| proxy_opts.no_proxy | string | False | | | 不应代理的主机的逗号分隔列表。| +| authorization_params | object | False | | | 在请求中发送到授权端点的附加参数。 | +| client_rsa_private_key | string | False | | | 用于签署 JWT 以向 OP 进行身份验证的客户端 RSA 私钥。当 `token_endpoint_auth_method` 为 `private_key_jwt` 时必需。 | +| client_rsa_private_key_id | string | False | | | 用于计算签名的 JWT 的客户端 RSA 私钥 ID。当 `token_endpoint_auth_method` 为 `private_key_jwt` 时可选。 | +| client_jwt_assertion_expires_in | integer | False | 60 | | 用于向 OP 进行身份验证的签名 JWT 的生命周期,以秒为单位。当 `token_endpoint_auth_method` 为 `private_key_jwt` 或 `client_secret_jwt` 时使用。 | +| renew_access_token_on_expiry | boolean | False | true | | 如果为 true,则在访问令牌过期或刷新令牌可用时尝试静默更新访问令牌。如果令牌无法更新,则重定向用户进行重新身份验证。| +| access_token_expires_in | integer | False | | | 如果令牌端点响应中不存在 `expires_in` 属性,则访问令牌的有效期(以秒为单位)。 | +| refresh_session_interval | integer | False | | | 刷新用户 ID 令牌而无需重新认证的时间间隔。如果未设置,则不会检查网关向客户端发出的会话的到期时间。如果设置为 900,则表示在 900 秒后刷新用户的 `id_token`(或浏览器中的会话),而无需重新认证。 | +| iat_slack | integer | False | 120 | | ID 令牌中 `iat` 声明的时钟偏差容忍度(以秒为单位)。 | +| accept_none_alg | boolean | False | false | | 如果 OpenID 提供程序未签署其 ID 令牌(例如当签名算法设置为`none` 时),则设置为 true。 | +| accept_unsupported_alg | boolean | False | true | | 如果为 true,则忽略 ID 令牌签名以接受不支持的签名算法。 | +| access_token_expires_leeway | integer | False | 0 | | 访问令牌续订的过期余地(以秒为单位)。当设置为大于 0 的值时,令牌续订将在令牌过期前设定的时间内进行。这样可以避免访问令牌在到达资源服务器时刚好过期而导致的错误。| +| force_reauthorize | boolean | False | false | | 如果为 true,即使令牌已被缓存,也执行授权流程。 | +| use_nonce | boolean | False | false | | 如果为 true,在授权请求中启用 nonce 参数。 | +| revoke_tokens_on_logout | boolean | False | false | | 如果为 true,则通知授权服务器,撤销端点不再需要先前获得的刷新或访问令牌。 | +| jwk_expires_in | integer | False | 86400 | | JWK 缓存的过期时间(秒)。 | +| jwt_verification_cache_ignore | boolean | False | false | | 如果为 true,则强制重新验证承载令牌并忽略任何现有的缓存验证结果。 | +| cache_segment | string | False | | | 缓存段的可选名称,用于分隔和区分令牌自检或 JWT 验证使用的缓存。| +| introspection_interval | integer | False | 0 | | 缓存和自省访问令牌的 TTL(以秒为单位)。默认值为 0,这意味着不使用此选项,插件默认使用 `introspection_expiry_claim` 中定义的到期声明传递的 TTL。如果`introspection_interval` 大于 0 且小于 `introspection_expiry_claim` 中定义的到期声明传递的 TTL,则使用`introspection_interval`。| +| introspection_expiry_claim | string | False | exp | | 到期声明的名称,它控制缓存和自省访问令牌的 TTL。| +| introspection_addon_headers | array[string] | False | | | 用于将其他标头值附加到自省 HTTP 请求。如果原始请求中不存在指定的标头,则不会附加值。| 注意:schema 中还定义了 `encrypt_fields = {"client_secret"}`,这意味着该字段将会被加密存储在 etcd 中。具体参考 [加密存储字段](../plugin-develop.md#加密存储字段)。 -## 使用场景 +## 示例 + +以下示例演示了如何针对不同场景配置 `openid-connect` 插件。 -:::tip +:::note + +您可以这样从 `config.yaml` 中获取 `admin_key` 并存入环境变量: -教程:[使用 Keycloak 与 API 网关保护你的 API](https://apisix.apache.org/zh/blog/2022/07/06/use-keycloak-with-api-gateway-to-secure-apis/) +```bash +admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g') +``` ::: -该插件提供两种使用场景: +### Authorization Code Flow -1. 应用之间认证授权:将 `bearer_only` 设置为 `true`,并配置 `introspection_endpoint` 或 `public_key` 属性。该场景下,请求头(Header)中没有令牌或无效令牌的请求将被拒绝。 +Authorization Code Flow 在 [RFC 6749,第 4.1 节](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1) 中定义。它涉及用临时授权码交换访问令牌,通常由机密和公共客户端使用。 -2. 浏览器中认证授权:将 `bearer_only` 设置为 `false`。认证成功后,该插件可获得并管理 Cookie 中的令牌,后续请求将使用该令牌。在这种模式中,用户 session 将作为 Cookie 存储在浏览器中,这些数据是加密的,因此你必须通过 `session.secret` 设置一个密钥用于加密。 +下图说明了实施 Authorization Code Flow 时不同实体之间的交互: -### 令牌内省 +![授权码流程图](https://static.api7.ai/uploads/2023/11/27/Ga2402sb_oidc-code-auth-flow-revised.png) -令牌内省是通过针对 OAuth 2.0 授权的服务器来验证令牌及相关请求,详情请阅读 [Token Introspection](https://www.oauth.com/oauth2-servers/token-introspection-endpoint/)。 +当传入请求的标头中或适当的会话 cookie 中不包含访问令牌时,插件将充当依赖方并重定向到授权服务器以继续授权码流程。 -首先,需要在身份认证服务器中创建受信任的客户端,并生成用于内省的有效令牌(JWT)。下图是通过网关进行令牌内省的成功示例流程: +成功验证后,插件将令牌保留在会话 cookie 中,后续请求将使用存储在 cookie 中的令牌。 -![token introspection](https://raw.githubusercontent.com/apache/apisix/master/docs/assets/images/plugin/oauth-1.png) +请参阅 [实现 Authorization Code Flow](../tutorials/keycloak-oidc.md#实现-authorization-code-grant)以获取使用`openid-connect`插件通过授权码流与 Keycloak 集成的示例。 -以下示例是在路由上启用插件。该路由将通过内省请求头中提供的令牌来保护上游: +### Proof Key for Code Exchange (PKCE) -:::note +Proof Key for Code Exchange (PKCE) 在 [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) 中定义。PKCE 通过添加代码质询和验证器来增强授权码流程,以防止授权码拦截攻击。 -您可以这样从 `config.yaml` 中获取 `admin_key` 并存入环境变量: +下图说明了使用 PKCE 实现授权码流程时不同实体之间的交互: -```bash -admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g') -``` +![使用 PKCE 的授权码流程图](https://static.api7.ai/uploads/2024/11/04/aJ2ZVuTC_auth-code-with-pkce.png) -::: +请参阅 [实现 Authorization Code Grant](../tutorials/keycloak-oidc.md#实现-authorization-code-grant),了解使用 `openid-connect` 插件通过 PKCE 授权码流程与 Keycloak 集成的示例。 -```shell -curl http://127.0.0.1:9180/apisix/admin/routes/1 \ --H "X-API-KEY: $admin_key" -X PUT -d ' -{ - "uri": "/get", - "plugins":{ - "openid-connect":{ - "client_id": "${CLIENT_ID}", - "client_secret": "${CLIENT_SECRET}", - "discovery": "${DISCOVERY_ENDPOINT}", - "introspection_endpoint": "${INTROSPECTION_ENDPOINT}", - "bearer_only": true, - "realm": "master", - "introspection_endpoint_auth_method": "client_secret_basic" - } - }, - "upstream":{ - "type": "roundrobin", - "nodes":{ - "httpbin.org:443":1 - } - } -}' -``` +### Client Credential Flow -以下命令可用于访问新路由: +Client Credential Flow 在 [RFC 6749,第 4.4 节](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4) 中定义。它涉及客户端使用自己的凭证请求访问令牌以访问受保护的资源,通常用于机器对机器身份验证,并不代表特定用户。 -```shell -curl -i -X GET http://127.0.0.1:9080/get -H "Authorization: Bearer {JWT_TOKEN}" -``` +下图说明了实施 Client Credential Flow 时不同实体之间的交互: -在此示例中,插件强制在请求头中设置访问令牌和 Userinfo 对象。 - -当 OAuth 2.0 授权服务器返回结果里除了令牌之外还有过期时间,其中令牌将在 APISIX 中缓存直至过期。更多信息请参考: - -1. [lua-resty-openidc](https://github.com/zmartzone/lua-resty-openidc) 的文档和源代码。 -2. `exp` 字段的定义:[Introspection Response](https://tools.ietf.org/html/rfc7662#section-2.2)。 - -### 公钥内省 - -除了令牌内省外,还可以使用 JWT 令牌的公钥进行验证。如果使用了公共密钥和令牌内省端点,就会执行公共密钥工作流,而不是通过身份服务器进行验证。该方式适可用于减少额外的网络调用并加快认证过程。 - -以下示例展示了如何将公钥添加到路由中: - -```shell -curl http://127.0.0.1:9180/apisix/admin/routes/1 \ --H "X-API-KEY: $admin_key" -X PUT -d ' -{ - "uri": "/get", - "plugins":{ - "openid-connect":{ - "client_id": "${CLIENT_ID}", - "client_secret": "${CLIENT_SECRET}", - "discovery": "${DISCOVERY_ENDPOINT}", - "bearer_only": true, - "realm": "master", - "token_signing_alg_values_expected": "RS256", - "public_key": "-----BEGIN PUBLIC KEY----- - {public_key} - -----END PUBLIC KEY-----" - } - }, - "upstream":{ - "type": "roundrobin", - "nodes":{ - "httpbin.org:443":1 - } - } -}' -``` +
+Client credential flow diagram +
+
-#### 通过 OIDC 依赖方认证流程进行身份验证 - -当一个请求在请求头或 session cookie 中不包含访问令牌时,该插件可以充当 OIDC 依赖方并重定向到身份提供者的授权端点以通过 [OIDC authorization code flow](https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth)。 - -一旦用户通过身份提供者进行身份验证,插件将代表用户从身份提供者获取和管理访问令牌和更多信息。该信息当前存储在 session cookie 中,该插件将会识别 Cookie 并使用其中的信息,以避免再次执行认证流程。 - -以下示例是将此操作模式添加到 Route: - -```shell -curl http://127.0.0.1:9180/apisix/admin/routes/1 \ --H "X-API-KEY: $admin_key" -X PUT -d ' -{ - "uri": "/get", - "plugins": { - "openid-connect": { - "client_id": "${CLIENT_ID}", - "client_secret": "${CLIENT_SECRET}", - "discovery": "${DISCOVERY_ENDPOINT}", - "bearer_only": false, - "realm": "master" - } - }, - "upstream": { - "type": "roundrobin", - "nodes": { - "httpbin.org:443": 1 - } - } -}' -``` +请参阅[实现 Client Credentials Grant](../tutorials/keycloak-oidc.md#实现-client-credentials-grant) 获取使用 `openid-connect` 插件通过客户端凭证流与 Keycloak 集成的示例。 + +### Introspection Flow -在以上示例中,该插件可以强制在配置的请求头中设置访问令牌、ID 令牌和 UserInfo 对象。 +Introspection Flow 在 [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662) 中定义。它涉及通过查询授权服务器的自省端点来验证访问令牌的有效性和详细信息。 + +在此流程中,当客户端向资源服务器出示访问令牌时,资源服务器会向授权服务器的自省端点发送请求,如果令牌处于活动状态,则该端点会响应令牌详细信息,包括令牌到期时间、相关范围以及它所属的用户或客户端等信息。 + +下图说明了使用令牌自省实现 Introspection Flow 时不同实体之间的交互: + +
+
+Client credential with introspection diagram +
+
+ +请参阅 [实现 Client Credentials Grant](../tutorials/keycloak-oidc.md#实现-client-credentials-grant) 以获取使用 `openid-connect` 插件通过带有令牌自省的客户端凭据流与 Keycloak 集成的示例。 + +### Password Flow + +Password Flow 在 [RFC 6749,第 4.3 节](https://datatracker.ietf.org/doc/html/rfc6749#section-4.3) 中定义。它专为受信任的应用程序而设计,允许它们使用用户的用户名和密码直接获取访问令牌。在此授权类型中,客户端应用程序将用户的凭据连同其自己的客户端 ID 和密钥一起发送到授权服务器,然后授权服务器对用户进行身份验证,如果有效,则颁发访问令牌。 + +虽然高效,但此流程仅适用于高度受信任的第一方应用程序,因为它要求应用程序直接处理敏感的用户凭据,如果在第三方环境中使用,则会带来重大安全风险。 + +下图说明了实施 Password Flow 时不同实体之间的交互: + +
+Password flow diagram +
+
+ +请参阅 [实现 Password Grant](../tutorials/keycloak-oidc.md#实现-password-grant) 获取使用 `openid-connect` 插件通过密码流与 Keycloak 集成的示例。 + +### Refresh Token Grant + +Refresh Token Grant 在 [RFC 6749,第 6 节](https://datatracker.ietf.org/doc/html/rfc6749#section-6) 中定义。它允许客户端使用之前颁发的刷新令牌请求新的访问令牌,而无需用户重新进行身份验证。此流程通常在访问令牌过期时使用,允许客户端无需用户干预即可持续访问资源。刷新令牌与某些 OAuth 流程中的访问令牌一起颁发,其使用寿命和安全要求取决于授权服务器的配置。 + +下图说明了在实施 Password Grant 和 Refresh Token Grant 时不同实体之间的交互: + +
+Password grant with refresh token flow diagram +
+
+ +请参阅 [Refresh Token](../tutorials/keycloak-oidc.md#refresh-token) 获取使用 `openid-connect` 插件通过带令牌刷新的密码流与 Keycloak 集成的示例。 ## 故障排除 @@ -278,3 +239,11 @@ upstream sent too big header while reading response header from upstream #### 5. 无效的客户端密钥 验证 `client_secret` 是否有效且正确。无效的 `client_secret` 将导致身份验证失败,并且不会返回任何令牌并将其存储在 session 中。 + +#### 6. PKCE IdP 配置 + +如果您使用授权码流程启用 PKCE,请确保您已将 IdP 客户端配置为使用 PKCE。例如,在 Keycloak 中,您应该在客户端的高级设置中配置 PKCE 质询方法: + +
+PKCE keycloak configuration +
diff --git a/docs/zh/latest/tutorials/keycloak-oidc.md b/docs/zh/latest/tutorials/keycloak-oidc.md new file mode 100644 index 000000000000..60afda86147c --- /dev/null +++ b/docs/zh/latest/tutorials/keycloak-oidc.md @@ -0,0 +1,467 @@ +--- +title: Set Up SSO with Keycloak (OIDC) +keywords: + - APISIX + - API 网关 + - OIDC + - Keycloak +description: 本文介绍如何使用 openid-connect 插件,通过 authorization code grant、client credentials grant 和 password grant 将 APISIX 与 Keycloak 集成。 +--- + + + +[OpenID Connect (OIDC)](https://openid.net/connect/) 是 [OAuth 2.0 协议](https://www.rfc-editor.org/rfc/rfc6749) 之上的简单身份层。它允许客户端基于身份提供者执行的身份验证来验证最终用户的身份,以及以可互操作和类似 REST 的方式获取有关最终​​用户的基本个人资料信息。借助 APISIX 和 [Keycloak](https://www.keycloak.org/),您可以实现基于 OIDC 的身份验证流程来保护您的 API 并启用单点登录 (SSO)。 + +[Keycloak](https://www.keycloak.org/) 是适用于现代应用程序和服务的开源身份和访问管理解决方案。Keycloak 支持单点登录 (SSO),这使得服务能够通过 OIDC 和 OAuth 2.0 等协议与 Keycloak 进行交互。此外,Keycloak 还支持将身份验证委托给第三方身份提供商,例如 Facebook 和 Google。 + +本教程将向您展示如何使用 [`openid-connect`](/hub/openid-connect) 插件,通过 [authorization code grant](#implement-authorization-code-grant)、[client credentials grant](#implement-client-credentials-grant) 和 [password grant](#implement-password-grant) 将 APISIX 与 Keycloak 集成。 + +## 配置 Keycloak + +在 Docker 中以 [开发模式](https://www.keycloak.org/server/configuration#_starting_keycloak_in_development_mode) 启动一个名为 `apisix-quickstart-keycloak` 的 Keycloak 实例,管理员名称为 `quickstart-admin`,密码为 `quickstart-admin-pass`,暴露的端口映射到宿主机上的 `8080`: + +```shell +docker run -d --name "apisix-quickstart-keycloak" \ + -e 'KEYCLOAK_ADMIN=quickstart-admin' \ + -e 'KEYCLOAK_ADMIN_PASSWORD=quickstart-admin-pass' \ + -p 8080:8080 \ + quay.io/keycloak/keycloak:18.0.2 start-dev +``` + +Keycloak 提供了一个易于使用的 Web UI,帮助管理员管理所有资源,例如客户端、角色和用户。 + +在浏览器中导航到 `http://localhost:8080` 以访问 Keycloak 网页,然后单击 __管理控制台__: + +![web-ui](https://static.api7.ai/uploads/2023/03/30/ItcwYPIx_web-ui.png) + +输入管理员用户名 `quickstart-admin` 和密码 `quickstart-admin-pass` 并登录: + +![admin-signin](https://static.api7.ai/uploads/2023/03/30/6W3pjzE1_admin-signin.png) + +您需要在以下步骤中保持登录状态来配置 Keycloak。 + +### 创建 Realm + +Keycloak 中的 realm 是管理用户、凭证和角色等资源的工作区。不同领域中的资源彼此隔离。您需要为 APISIX 创建一个名为`quickstart-realm` 的 realm。 + +在左侧菜单中,将鼠标悬停在 **Master** 上,然后在下拉菜单中选择 __Add realm__: + +![create-realm](https://static.api7.ai/uploads/2023/03/30/S1Xvqliv_create-realm.png) + +输入 realm 名称 `quickstart-realm`,然后单击 `__Create__` 进行创建: + +![add-realm](https://static.api7.ai/uploads/2023/03/30/jwb7QU8k_add-realm.png) + +### 创建 Client + +Keycloak 中的 client 是请求 Keycloak 对用户进行身份验证的实体。更多情况下,client 是希望使用 Keycloak 保护自身安全并提供单点登录解决方案的应用程序。APISIX 相当于负责向 Keycloak 发起身份验证请求的 client,因此您需要创建其对应的客户端,名为 `apisix-quickstart-client`。 + +单击 __Clients__ > __Create__,打开 __Add Client__ 页面: + +![create-client](https://static.api7.ai/uploads/2023/03/30/qLom0axN_create-client.png) + +输入 __Client ID__ 为 `apisix-quickstart-client`,然后选择 __Client Protocol__ 为 `openid-connect` 并 __Save__: + +![add-client](https://static.api7.ai/uploads/2023/03/30/X5on2r7x_add-client.png) + +Client `apisix-quickstart-client` 已创建。重定向到详细信息页面后,选择 `confidential` 作为 __Access Type__: + +![config-client](https://static.api7.ai/uploads/2023/03/30/v70c8y9F_config-client.png) + +当用户在 SSO 期间登录成功时,Keycloak 会携带状态和代码将客户端重定向到 __Valid Redirect URIs__ 中的地址。为简化操作,输入通配符 `*` 以将任何 URI 视为有效: + +![client-redirect](https://static.api7.ai/uploads/2023/03/30/xLxcyVkn_client-redirect.png) + +如果您正在 [使用 PKCE authorization code grant](#implement-authorization-code-grant),请在客户端的高级设置中配置 PKCE 质询方法: + +
+PKCE keycloak configuration +
+ +如果您正在实施 [client credentials grant](#implement-client-credentials-grant),请为 client 启用服务帐户: + +![enable-service-account](https://static.api7.ai/uploads/2023/12/29/h1uNtghd_sa.png) + +选择 __Save__ 以应用自定义配置。 + +### 创建 User + +Keycloak 中的用户是能够登录系统的实体。他们可以拥有与自己相关的属性,例如用户名、电子邮件和地址。 + +如果您只实施 [client credentials grant](#implement-client-credentials-grant),则可以 [跳过此部分](#obtain-the-oidc-configuration)。 + +点击 __Users__ > __Add user__ 打开 __Add user__ 页面: + +![create-user](https://static.api7.ai/uploads/2023/03/30/onQEp23L_create-user.png) + +点击 __Users__ > __Add user__ 打开 __Add user__ 页面: + +![add-user](https://static.api7.ai/uploads/2023/03/30/EKhuhgML_add-user.png) + +点击 __Credentials__,然后将 __Password__ 设置为 `quickstart-user-pass`。将 __Temporary__ 切换为 `OFF` 以关闭限制,这样您第一次登录时就无需更改密码: + +![user-pass](https://static.api7.ai/uploads/2023/03/30/rQKEAEnh_user-pass.png) + +## 获取 OIDC 配置 + +在本节中,您将从 Keycloak 获取关键的 OIDC 配置并将其定义为 shell 变量。本节之后的步骤将使用这些变量通过 shell 命令配置 OIDC。 + +:::info + +打开一个单独的终端按照步骤操作并定义相关的 shell 变量。然后本节之后的步骤可以直接使用定义的变量。 + +::: + +### 获取发现端点 + +单击 __Realm Settings__,然后右键单击 __OpenID Endpoints Configuration__ 并复制链接。 + +![get-discovery](https://static.api7.ai/uploads/2023/03/30/526lbJbg_get-discovery.png) + +该链接应与以下内容相同: + +```text +http://localhost:8080/realms/quickstart-realm/.well-known/openid-configuration +``` + +在 OIDC 身份验证期间需要使用此端点公开的配置值。使用您的主机 IP 更新地址并保存到环境变量: + +```shell +export KEYCLOAK_IP=192.168.42.145 # replace with your host IP +export OIDC_DISCOVERY=http://${KEYCLOAK_IP}:8080/realms/quickstart-realm/.well-known/openid-configuration +``` + +### 获取客户端 ID 和密钥 + +单击 __Clients__ > `apisix-quickstart-client` > __Credentials__,并从 __Secret__ 复制客户端密钥: + +![client-ID](https://static.api7.ai/uploads/2023/03/30/MwYmU20v_client-id.png) + +![client-secret](https://static.api7.ai/uploads/2023/03/30/f9iOG8aN_client-secret.png) + +将 OIDC 客户端 ID 和密钥保存到环境变量: + +```shell +export OIDC_CLIENT_ID=apisix-quickstart-client +export OIDC_CLIENT_SECRET=bSaIN3MV1YynmtXvU8lKkfeY0iwpr9cH # replace with your value +``` + +## 实现 Authorization Code Grant + +Authorization Code Grant 由 Web 和移动应用程序使用。流程从授权服务器在浏览器中显示登录页面开始,用户可以在其中输入其凭据。在此过程中,将短期授权码交换为访问令牌,APISIX 将其存储在浏览器会话 cookie 中,并将随访问上游资源服务器的每次请求一起发送。 + +要实现 Authorization Code Grant,请使用 `openid-connect` 插件创建一个路由,如下所示: + +```shell +curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d ' +{ + "id": "auth-with-oidc", + "uri":"/anything/*", + "plugins": { + "openid-connect": { + "bearer_only": false, + "client_id": "'"$OIDC_CLIENT_ID"'", + "client_secret": "'"$OIDC_CLIENT_SECRET"'", + "discovery": "'"$OIDC_DISCOVERY"'", + "scope": "openid profile", + "redirect_uri": "http://localhost:9080/anything/callback" + } + }, + "upstream":{ + "type":"roundrobin", + "nodes":{ + "httpbin.org:80":1 + } + } +}' +``` + +或者,如果您想使用 PKCE 实现 authorization code grant,请使用 `openid-connect` 插件创建一个路由如下: + +```shell +curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d ' +{ + "id": "auth-with-oidc", + "uri":"/anything/*", + "plugins": { + "openid-connect": { + "bearer_only": false, + "use_pkce": true, + "client_id": "'"$OIDC_CLIENT_ID"'", + "client_secret": "'"$OIDC_CLIENT_SECRET"'", + "discovery": "'"$OIDC_DISCOVERY"'", + "scope": "openid profile", + "redirect_uri": "http://localhost:9080/anything/callback" + } + }, + "upstream":{ + "type":"roundrobin", + "nodes":{ + "httpbin.org:80":1 + } + } +}' +``` + +### 使用有效凭证进行验证 + +在浏览器中导航至 `http://127.0.0.1:9080/anything/test`。请求将重定向到登录页面: + +![test-sign-on](https://static.api7.ai/uploads/2023/03/30/i38u1x9a_validate-sign.png) + +使用正确的用户名 `quickstart-user` 和密码 `quickstart-user-pass` 登录。如果成功,请求将被转发到 `httpbin.org`,您应该会看到类似以下内容的响应: + +```json +{ + "args": {}, + "data": "", + "files": {}, + "form": {}, + "headers": { + "Accept": "text/html..." + ... + }, + "json": null, + "method": "GET", + "origin": "127.0.0.1, 59.71.244.81", + "url": "http://127.0.0.1/anything/test" +} +``` + +### 使用无效凭证进行验证 + +使用错误的凭证登录。您应该会看到身份验证失败: + +![test-sign-failed](https://static.api7.ai/uploads/2023/03/31/YOuSYX1r_validate-sign-failed.png) + +## 实现 Client Credential Grant + +在 client credential grant 中,客户端无需任何用户参与即可获得访问令牌。它通常用于机器对机器 (M2M) 通信。 + +要实现 client credential grant,请使用 `openid-connect` 插件创建路由,以使用身份提供者的 JWKS 端点来验证令牌。端点将从发现文档中获取。 + +```shell +curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d ' +{ + "id": "auth-with-oidc", + "uri":"/anything/*", + "plugins": { + "openid-connect": { + "use_jwks": true, + "client_id": "'"$OIDC_CLIENT_ID"'", + "client_secret": "'"$OIDC_CLIENT_SECRET"'", + "discovery": "'"$OIDC_DISCOVERY"'", + "scope": "openid profile", + "redirect_uri": "http://localhost:9080/anything/callback" + } + }, + "upstream":{ + "type":"roundrobin", + "nodes":{ + "httpbin.org:80":1 + } + } +}' +``` + +或者,如果您想使用自省端点来验证令牌,请按如下方式创建路由: + +```shell +curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d ' +{ + "id": "auth-with-oidc", + "uri":"/anything/*", + "plugins": { + "openid-connect": { + "bearer_only": true, + "client_id": "'"$OIDC_CLIENT_ID"'", + "client_secret": "'"$OIDC_CLIENT_SECRET"'", + "discovery": "'"$OIDC_DISCOVERY"'", + "scope": "openid profile", + "redirect_uri": "http://localhost:9080/anything/callback" + } + }, + "upstream":{ + "type":"roundrobin", + "nodes":{ + "httpbin.org:80":1 + } + } +}' +``` + +自省端点将从发现文档中获取。 + +### 使用有效访问令牌进行验证 + +在 [令牌端点](https://www.keycloak.org/docs/latest/securing_apps/#token-endpoint) 获取 Keycloak 服务器的访问令牌: + +```shell +curl -i "http://$KEYCLOAK_IP:8080/realms/quickstart-realm/protocol/openid-connect/token" -X POST \ + -d 'grant_type=client_credentials' \ + -d 'client_id='$OIDC_CLIENT_ID'' \ + -d 'client_secret='$OIDC_CLIENT_SECRET'' +``` + +预期响应类似于以下内容: + +```text +{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJoT3ludlBPY2d6Y3VWWnYtTU42bXZKMUczb0dOX2d6MFo3WFl6S2FSa1NBIn0.eyJleHAiOjE3MDM4MjU1NjQsImlhdCI6MTcwMzgyNTI2NCwianRpIjoiMWQ4NWE4N2UtZDFhMC00NThmLThiMTItNGZiYWM2ODA5YmYwIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMS44Mzo4MDgwL3JlYWxtcy9xdWlja3N0YXJ0LXJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjE1OGUzOWFlLTk0YjAtNDI3Zi04ZGU3LTU3MTRhYWYwOGYzOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1xdWlja3N0YXJ0LXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50SG9zdCI6IjE3Mi4xNy4wLjEiLCJjbGllbnRJZCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1hcGlzaXgtcXVpY2tzdGFydC1jbGllbnQiLCJjbGllbnRBZGRyZXNzIjoiMTcyLjE3LjAuMSJ9.TltzSXqrJuVID7aGrb35jn-oc07U_-jugSn-3jKz4A44LwtAsME_8b3qkmR4boMOIht_5pF6bnnp70MFAlg6JKu4_yIQDxF_GAHjnZXEO8OCKhtIKwXm2w-hnnJVIhIdGkIVkbPP0HfILuar_m0hpa53VpPBGYR-OS4pyh0KTUs8MB22xAEqyz9zjCm6SX9vXCqgeVkSpRW2E8NaGEbAdY25uY-ZC4dI_pON87Ey5e8GdD6HQLXQlGIOdCDi3N7k0HDoD9TZRv2bMRPfy4zVYm1ZlClIuF79A-ZBwr0c-XYuq7t6EY0gPGEXB-s0SaKlrIU5S9JBeVXRzYvqAih41g","expires_in":300,"refresh_expires_in":0,"token_type":"Bearer","not-before-policy":0,"scope":"email profile"} +``` + +将访问令牌保存到环境变量: + +```shell +# replace with your access token +export ACCESS_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJoT3ludlBPY2d6Y3VWWnYtTU42bXZKMUczb0dOX2d6MFo3WFl6S2FSa1NBIn0.eyJleHAiOjE3MDM4MjU1NjQsImlhdCI6MTcwMzgyNTI2NCwianRpIjoiMWQ4NWE4N2UtZDFhMC00NThmLThiMTItNGZiYWM2ODA5YmYwIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMS44Mzo4MDgwL3JlYWxtcy9xdWlja3N0YXJ0LXJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjE1OGUzOWFlLTk0YjAtNDI3Zi04ZGU3LTU3MTRhYWYwOGYzOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1xdWlja3N0YXJ0LXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiY2xpZW50SG9zdCI6IjE3Mi4xNy4wLjEiLCJjbGllbnRJZCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1hcGlzaXgtcXVpY2tzdGFydC1jbGllbnQiLCJjbGllbnRBZGRyZXNzIjoiMTcyLjE3LjAuMSJ9.TltzSXqrJuVID7aGrb35jn-oc07U_-jugSn-3jKz4A44LwtAsME_8b3qkmR4boMOIht_5pF6bnnp70MFAlg6JKu4_yIQDxF_GAHjnZXEO8OCKhtIKwXm2w-hnnJVIhIdGkIVkbPP0HfILuar_m0hpa53VpPBGYR-OS4pyh0KTUs8MB22xAEqyz9zjCm6SX9vXCqgeVkSpRW2E8NaGEbAdY25uY-ZC4dI_pON87Ey5e8GdD6HQLXQlGIOdCDi3N7k0HDoD9TZRv2bMRPfy4zVYm1ZlClIuF79A-ZBwr0c-XYuq7t6EY0gPGEXB-s0SaKlrIU5S9JBeVXRzYvqAih41g" +``` + +使用有效的访问令牌向路由发送请求: + +```shell +curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer $ACCESS_TOKEN" +``` + +`HTTP/1.1 200 OK` 响应验证对上游资源的请求是否已获得授权。 + +### 使用无效访问令牌进行验证 + +使用无效访问令牌向路由发送请求: + +```shell +curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer invalid-access-token" +``` + +`HTTP/1.1 401 Unauthorized` 响应验证 OIDC 插件是否拒绝了具有无效访问令牌的请求。 + +### 验证无访问令牌 + +向无访问令牌的路由发送请求: + +```shell +curl -i "http://127.0.0.1:9080/anything/test" +``` + +`HTTP/1.1 401 Unauthorized` 响应验证 OIDC 插件拒绝没有访问令牌的请求。 + +## 实施 Password Grant + +Password Grant 是一种将用户凭据交换为访问令牌的传统方法。 + +要实施 Password Grant,请使用 `openid-connect` 插件创建路由,以使用身份提供者的 JWKS 端点来验证令牌。端点将从发现文档中获取。 + +```shell +curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d ' +{ + "id": "auth-with-oidc", + "uri":"/anything/*", + "plugins": { + "openid-connect": { + "use_jwks": true, + "client_id": "'"$OIDC_CLIENT_ID"'", + "client_secret": "'"$OIDC_CLIENT_SECRET"'", + "discovery": "'"$OIDC_DISCOVERY"'", + "scope": "openid profile", + "redirect_uri": "http://localhost:9080/anything/callback" + } + }, + "upstream":{ + "type":"roundrobin", + "nodes":{ + "httpbin.org:80":1 + } + } +}' +``` + +### 使用有效访问令牌进行验证 + +在 [令牌端点](https://www.keycloak.org/docs/latest/securing_apps/#token-endpoint) 获取 Keycloak 服务器的访问令牌: + +```shell +OIDC_USER=quickstart-user +OIDC_PASSWORD=quickstart-user-pass +curl -i "http://$KEYCLOAK_IP:8080/realms/quickstart-realm/protocol/openid-connect/token" -X POST \ + -d 'grant_type=password' \ + -d 'client_id='$OIDC_CLIENT_ID'' \ + -d 'client_secret='$OIDC_CLIENT_SECRET'' \ + -d 'username='$OIDC_USER'' \ + -d 'password='$OIDC_PASSWORD'' +``` + +预期响应类似于以下内容: + +```text +{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ6U3FFaXN6VlpuYi1sRWMzZkp0UHNpU1ZZcGs4RGN3dXI1Mkx5V05aQTR3In0.eyJleHAiOjE2ODAxNjA5NjgsImlhdCI6MTY4MDE2MDY2OCwianRpIjoiMzQ5MTc4YjQtYmExZC00ZWZjLWFlYTUtZGY2MzJiMDJhNWY5IiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguNDIuMTQ1OjgwODAvcmVhbG1zL3F1aWNrc3RhcnQtcmVhbG0iLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTg4MTVjM2EtNmQwNy00YTY2LWJjZjItYWQ5NjdmMmIwMTFmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYXBpc2l4LXF1aWNrc3RhcnQtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6ImIxNmIyNjJlLTEwNTYtNDUxNS1hNDU1LWYyNWUwNzdjY2I3NiIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1xdWlja3N0YXJ0LXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6ImIxNmIyNjJlLTEwNTYtNDUxNS1hNDU1LWYyNWUwNzdjY2I3NiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoicXVpY2tzdGFydC11c2VyIn0.uD_7zfZv5182aLXu9-YBzBDK0nr2mE4FWb_4saTog2JTqFTPZZa99Gm8AIDJx2ZUcZ_ElkATqNUZ4OpWmL2Se5NecMw3slJReewjD6xgpZ3-WvQuTGpoHdW5wN9-Rjy8ungilrnAsnDA3tzctsxm2w6i9KISxvZrzn5Rbk-GN6fxH01VC5eekkPUQJcJgwuJiEiu70SjGnm21xDN4VGkNRC6jrURoclv3j6AeOqDDIV95kA_MTfBswDFMCr2PQlj5U0RTndZqgSoxwFklpjGV09Azp_jnU7L32_Sq-8coZd0nj5mSdbkJLJ8ZDQDV_PP3HjCP7EHdy4P6TyZ7oGvjw","expires_in":300,"refresh_expires_in":1800,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0YjFiNTQ3Yi0zZmZjLTQ5YzQtYjE2Ni03YjdhNzIxMjk1ODcifQ.eyJleHAiOjE2ODAxNjI0NjgsImlhdCI6MTY4MDE2MDY2OCwianRpIjoiYzRjNjNlMTEtZTdlZS00ZmEzLWJlNGYtNDMyZWQ4ZmY5OTQwIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguNDIuMTQ1OjgwODAvcmVhbG1zL3F1aWNrc3RhcnQtcmVhbG0iLCJhdWQiOiJodHRwOi8vMTkyLjE2OC40Mi4xNDU6ODA4MC9yZWFsbXMvcXVpY2tzdGFydC1yZWFsbSIsInN1YiI6IjE4ODE1YzNhLTZkMDctNGE2Ni1iY2YyLWFkOTY3ZjJiMDExZiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJhcGlzaXgtcXVpY2tzdGFydC1jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiYjE2YjI2MmUtMTA1Ni00NTE1LWE0NTUtZjI1ZTA3N2NjYjc2Iiwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiYjE2YjI2MmUtMTA1Ni00NTE1LWE0NTUtZjI1ZTA3N2NjYjc2In0.8xYP4bhDg1U9B5cTaEVD7B4oxNp8wwAYEynUne_Jm78","token_type":"Bearer","not-before-policy":0,"session_state":"b16b262e-1056-4515-a455-f25e077ccb76","scope":"profile email"} +``` + +将访问令牌和刷新令牌保存到环境变量中。刷新令牌将在刷新令牌步骤中使用。 + +```shell +# replace with your access token +export ACCESS_TOKEN="eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ6U3FFaXN6VlpuYi1sRWMzZkp0UHNpU1ZZcGs4RGN3dXI1Mkx5V05aQTR3In0.eyJleHAiOjE2ODAxNjA5NjgsImlhdCI6MTY4MDE2MDY2OCwianRpIjoiMzQ5MTc4YjQtYmExZC00ZWZjLWFlYTUtZGY2MzJiMDJhNWY5IiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguNDIuMTQ1OjgwODAvcmVhbG1zL3F1aWNrc3RhcnQtcmVhbG0iLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiMTg4MTVjM2EtNmQwNy00YTY2LWJjZjItYWQ5NjdmMmIwMTFmIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiYXBpc2l4LXF1aWNrc3RhcnQtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6ImIxNmIyNjJlLTEwNTYtNDUxNS1hNDU1LWYyNWUwNzdjY2I3NiIsImFjciI6IjEiLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1xdWlja3N0YXJ0LXJlYWxtIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6ImIxNmIyNjJlLTEwNTYtNDUxNS1hNDU1LWYyNWUwNzdjY2I3NiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoicXVpY2tzdGFydC11c2VyIn0.uD_7zfZv5182aLXu9-YBzBDK0nr2mE4FWb_4saTog2JTqFTPZZa99Gm8AIDJx2ZUcZ_ElkATqNUZ4OpWmL2Se5NecMw3slJReewjD6xgpZ3-WvQuTGpoHdW5wN9-Rjy8ungilrnAsnDA3tzctsxm2w6i9KISxvZrzn5Rbk-GN6fxH01VC5eekkPUQJcJgwuJiEiu70SjGnm21xDN4VGkNRC6jrURoclv3j6AeOqDDIV95kA_MTfBswDFMCr2PQlj5U0RTndZqgSoxwFklpjGV09Azp_jnU7L32_Sq-8coZd0nj5mSdbkJLJ8ZDQDV_PP3HjCP7EHdy4P6TyZ7oGvjw" +export REFRESH_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0YjFiNTQ3Yi0zZmZjLTQ5YzQtYjE2Ni03YjdhNzIxMjk1ODcifQ.eyJleHAiOjE2ODAxNjI0NjgsImlhdCI6MTY4MDE2MDY2OCwianRpIjoiYzRjNjNlMTEtZTdlZS00ZmEzLWJlNGYtNDMyZWQ4ZmY5OTQwIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguNDIuMTQ1OjgwODAvcmVhbG1zL3F1aWNrc3RhcnQtcmVhbG0iLCJhdWQiOiJodHRwOi8vMTkyLjE2OC40Mi4xNDU6ODA4MC9yZWFsbXMvcXVpY2tzdGFydC1yZWFsbSIsInN1YiI6IjE4ODE1YzNhLTZkMDctNGE2Ni1iY2YyLWFkOTY3ZjJiMDExZiIsInR5cCI6IlJlZnJlc2giLCJhenAiOiJhcGlzaXgtcXVpY2tzdGFydC1jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiYjE2YjI2MmUtMTA1Ni00NTE1LWE0NTUtZjI1ZTA3N2NjYjc2Iiwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiYjE2YjI2MmUtMTA1Ni00NTE1LWE0NTUtZjI1ZTA3N2NjYjc2In0.8xYP4bhDg1U9B5cTaEVD7B4oxNp8wwAYEynUne_Jm78" +``` + +使用有效的访问令牌向路由发送请求: + +```shell +curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer $ACCESS_TOKEN" +``` + +`HTTP/1.1 200 OK` 响应验证对上游资源的请求是否已获得授权。 + +### 使用无效访问令牌进行验证 + +使用无效访问令牌向路由发送请求: + +```shell +curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer invalid-access-token" +``` + +`HTTP/1.1 401 Unauthorized` 响应验证 OIDC 插件是否拒绝了具有无效访问令牌的请求。 + +### 验证无访问令牌 + +向无访问令牌的路由发送请求: + +```shell +curl -i "http://127.0.0.1:9080/anything/test" +``` + +`HTTP/1.1 401 Unauthorized` 响应验证 OIDC 插件拒绝没有访问令牌的请求。 + +### 刷新令牌 + +要刷新访问令牌,请向 Keycloak 令牌端点发送请求,如下所示: + +```shell +curl -i "http://$KEYCLOAK_IP:8080/realms/quickstart-realm/protocol/openid-connect/token" -X POST \ + -d 'grant_type=refresh_token' \ + -d 'client_id='$OIDC_CLIENT_ID'' \ + -d 'client_secret='$OIDC_CLIENT_SECRET'' \ + -d 'refresh_token='$REFRESH_TOKEN'' +``` + +您应该看到类似以下的响应,其中包含新的访问令牌和刷新令牌,您可以将其用于后续请求和令牌刷新: + +```text +{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJTdnVwLXlPMHhDdTJBVi1za2pCZ0h6SHZNaG1mcDVDQWc0NHpYb2QxVTlNIn0.eyJleHAiOjE3MzAyNzQ3NDUsImlhdCI6MTczMDI3NDQ0NSwianRpIjoiMjk2Mjk5MWUtM2ExOC00YWFiLWE0NzAtODgxNWEzNjZjZmM4IiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMTUyLjU6ODA4MC9yZWFsbXMvcXVpY2tzdGFydC1yZWFsbSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiI2ZWI0ZTg0Yy00NmJmLTRkYzUtOTNkMC01YWM5YzE5MWU0OTciLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhcGlzaXgtcXVpY2tzdGFydC1jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiNTU2ZTQyYjktMjE2Yi00NTEyLWE5ZjAtNzE3ZTAyYTQ4MjZhIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLXF1aWNrc3RhcnQtcmVhbG0iLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiNTU2ZTQyYjktMjE2Yi00NTEyLWE5ZjAtNzE3ZTAyYTQ4MjZhIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJxdWlja3N0YXJ0LXVzZXIifQ.KLqn1LQdazoPBqLLR856C35XpqbMO9I7WFt3KrDxZF1N8vwv4AvZYWI_2rsbdjCakh9JmPgyYRgEGufYLiDBsqy9CrMVejAIJPYsJIonIXBCp5Ysu92ODJuqtTKuuJ6K7dam7fisBFfCBbVvGspnZ3p0caedpOaF_kSd-F8ARHKVsmkuX3_ucDrP3UctjEXHezefTY4YHjNMB9wuMDPXX2vXt2BsOasnznsIHHHX-ZH8JY6eEfWPtfx0qAED6lVZICT6Rqj_j5-Cf9ogzFtLyy_XvtG9BbHME2B8AXYpxdzqxOxmVVbZdrB8elfmFjs1R3vUn2r3xA9hO_znZo_IoQ","expires_in":300,"refresh_expires_in":1800,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIwYWYwZTAwYy0xMThjLTRkNDktYmIwMS1iMDIwNDE3MmFjMzIifQ.eyJleHAiOjE3MzAyNzYyNDUsImlhdCI6MTczMDI3NDQ0NSwianRpIjoiZGQyZTJmYTktN2Y3Zi00MjM5LWEwODAtNWQyZDFiZTdjNzk4IiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMTUyLjU6ODA4MC9yZWFsbXMvcXVpY2tzdGFydC1yZWFsbSIsImF1ZCI6Imh0dHA6Ly8xOTIuMTY4LjE1Mi41OjgwODAvcmVhbG1zL3F1aWNrc3RhcnQtcmVhbG0iLCJzdWIiOiI2ZWI0ZTg0Yy00NmJmLTRkYzUtOTNkMC01YWM5YzE5MWU0OTciLCJ0eXAiOiJSZWZyZXNoIiwiYXpwIjoiYXBpc2l4LXF1aWNrc3RhcnQtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjU1NmU0MmI5LTIxNmItNDUxMi1hOWYwLTcxN2UwMmE0ODI2YSIsInNjb3BlIjoiZW1haWwgcHJvZmlsZSIsInNpZCI6IjU1NmU0MmI5LTIxNmItNDUxMi1hOWYwLTcxN2UwMmE0ODI2YSJ9.Uad4BVuojHfyxqedFT5BHliWjIqVDbjM-Xeme0G2AAg","token_type":"Bearer","not-before-policy":0,"session_state":"556e42b9-216b-4512-a9f0-717e02a4826a","scope":"email profile"} +``` From 9bec67aece3e7123a2a6d697354e9db03f1b16d4 Mon Sep 17 00:00:00 2001 From: traky Date: Mon, 6 Jan 2025 15:00:22 +0800 Subject: [PATCH 2/5] lint --- docs/en/latest/tutorials/keycloak-oidc.md | 2 +- docs/zh/latest/tutorials/keycloak-oidc.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/latest/tutorials/keycloak-oidc.md b/docs/en/latest/tutorials/keycloak-oidc.md index ac9c58b44992..64e2ecff8ad0 100644 --- a/docs/en/latest/tutorials/keycloak-oidc.md +++ b/docs/en/latest/tutorials/keycloak-oidc.md @@ -131,7 +131,7 @@ Open a separate terminal to follow the steps and define related shell variables. ### Get Discovery Endpoint -Click __Realm Settings__, then right click __OpenID Endpoints Configuration__ and copy the link. +Click __Realm Settings__, then right click __OpenID Endpoints Configuration__ and copy the link. ![get-discovery](https://static.api7.ai/uploads/2023/03/30/526lbJbg_get-discovery.png) diff --git a/docs/zh/latest/tutorials/keycloak-oidc.md b/docs/zh/latest/tutorials/keycloak-oidc.md index 60afda86147c..94acab7d5cca 100644 --- a/docs/zh/latest/tutorials/keycloak-oidc.md +++ b/docs/zh/latest/tutorials/keycloak-oidc.md @@ -85,7 +85,7 @@ Client `apisix-quickstart-client` 已创建。重定向到详细信息页面后 ![config-client](https://static.api7.ai/uploads/2023/03/30/v70c8y9F_config-client.png) -当用户在 SSO 期间登录成功时,Keycloak 会携带状态和代码将客户端重定向到 __Valid Redirect URIs__ 中的地址。为简化操作,输入通配符 `*` 以将任何 URI 视为有效: +当用户在 SSO 期间登录成功时,Keycloak 会携带状态和代码将客户端重定向到 __Valid Redirect URIs__ 中的地址。为简化操作,输入通配符 `*` 以将任何 URI 视为有效: ![client-redirect](https://static.api7.ai/uploads/2023/03/30/xLxcyVkn_client-redirect.png) From f7c107a673df6f327bb7e65a709168b25e5ea848 Mon Sep 17 00:00:00 2001 From: traky Date: Mon, 6 Jan 2025 15:03:03 +0800 Subject: [PATCH 3/5] update config.json --- docs/en/latest/config.json | 3 ++- docs/zh/latest/config.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json index a17a6ae48f7d..067b26f964ff 100644 --- a/docs/en/latest/config.json +++ b/docs/en/latest/config.json @@ -39,7 +39,8 @@ "tutorials/cache-api-responses", "tutorials/add-multiple-api-versions", "tutorials/client-to-apisix-mtls", - "tutorials/websocket-authentication" + "tutorials/websocket-authentication", + "tutorials/keycloak-oidc" ] }, { diff --git a/docs/zh/latest/config.json b/docs/zh/latest/config.json index d3a8c71232ce..8364cf5b4099 100644 --- a/docs/zh/latest/config.json +++ b/docs/zh/latest/config.json @@ -28,7 +28,8 @@ "tutorials/protect-api", "tutorials/observe-your-api", "tutorials/health-check", - "tutorials/client-to-apisix-mtls" + "tutorials/client-to-apisix-mtls", + "tutorials/keycloak-oidc" ] }, { From f534a67703ea530954f2d87e367d2c996b381db5 Mon Sep 17 00:00:00 2001 From: traky Date: Mon, 6 Jan 2025 15:16:30 +0800 Subject: [PATCH 4/5] lint --- docs/en/latest/plugins/openid-connect.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/latest/plugins/openid-connect.md b/docs/en/latest/plugins/openid-connect.md index 6545e296e6d9..c61ae947a6b7 100644 --- a/docs/en/latest/plugins/openid-connect.md +++ b/docs/en/latest/plugins/openid-connect.md @@ -71,8 +71,8 @@ The `openid-connect` Plugin supports the integration with [OpenID Connect (OIDC) | proxy_opts | object | False | | | Configurations for the proxy server that the OpenID provider is behind. | | proxy_opts.http_proxy | string | False | | | Proxy server address for HTTP requests, such as `http://:`. | | proxy_opts.https_proxy | string | False | | | Proxy server address for HTTPS requests, such as `http://:`. | -| proxy_opts.http_proxy_authorization | string | False | | Basic [base64 username:password] | Default `Proxy-Authorization` header value to be used with `http_proxy`. Can be overriden with custom `Proxy-Authorization` request header. | -| proxy_opts.https_proxy_authorization | string | False | | Basic [base64 username:password] | Default `Proxy-Authorization` header value to be used with `https_proxy`. Cannot be overriden with custom `Proxy-Authorization` request header since with HTTPS, the authorization is completed when connecting. | +| proxy_opts.http_proxy_authorization | string | False | | Basic [base64 username:password] | Default `Proxy-Authorization` header value to be used with `http_proxy`. Can be overridden with custom `Proxy-Authorization` request header. | +| proxy_opts.https_proxy_authorization | string | False | | Basic [base64 username:password] | Default `Proxy-Authorization` header value to be used with `https_proxy`. Cannot be overridden with custom `Proxy-Authorization` request header since with HTTPS, the authorization is completed when connecting. | | proxy_opts.no_proxy | string | False | | | Comma separated list of hosts that should not be proxied. | | authorization_params | object | False | | | Additional parameters to send in the request to the authorization endpoint. | | client_rsa_private_key | string | False | | | Client RSA private key used to sign JWT for authentication to the OP. Required when `token_endpoint_auth_method` is `private_key_jwt`. | From 7d6ecec2ca6d42f0eb4743142a712c361b4c5073 Mon Sep 17 00:00:00 2001 From: Traky Deng Date: Fri, 17 Jan 2025 16:14:10 +0800 Subject: [PATCH 5/5] update zh doc --- docs/zh/latest/plugins/openid-connect.md | 106 +++++++++++------------ 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/docs/zh/latest/plugins/openid-connect.md b/docs/zh/latest/plugins/openid-connect.md index 58fbb86d0bed..96ec0d426035 100644 --- a/docs/zh/latest/plugins/openid-connect.md +++ b/docs/zh/latest/plugins/openid-connect.md @@ -41,59 +41,59 @@ description: openid-connect 插件支持与 OpenID Connect (OIDC) 身份提供 | ------------------------------------ | ------- | ------ | --------------------- | ------------- | ------------------------------------------------------------------------------------------------ | | client_id | string | 是 | | | OAuth 客户端 ID。 | | client_secret | string | 是 | | | OAuth 客户端 secret。 | -| discovery | string | True | | | OpenID 提供商的知名发现文档的 URL,其中包含 OP API 端点列表。插件可以直接利用发现文档中的端点。您也可以单独配置这些端点,这优先于发现文档中提供的端点。 | -| scope | string | False | openid | | 与应返回的有关经过身份验证的用户的信息相对应的 OIDC 范围,也称为 [claim](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims)。这用于向用户授权适当的权限。默认值为 `openid`,这是 OIDC 返回唯一标识经过身份验证的用户的 `sub` 声明所需的范围。可以附加其他范围并用空格分隔,例如 `openid email profile`。 | -| required_scopes | array[string] | False | | | 访问令牌中必须存在的范围。当 `bearer_only` 为 `true` 时与自省端点结合使用。如果缺少任何必需的范围,插件将以 403 禁止错误拒绝请求。| -| realm | string | False | apisix | | 由于持有者令牌无效,[`WWW-Authenticate`](https://www.rfc-editor.org/rfc/rfc6750#section-3) 响应标头中的领域伴随 401 未经授权的请求。 | -| bearer_only | boolean | False | false | | 如果为 true,则严格要求在身份验证请求中使用持有者访问令牌。 | -| logout_path | string | False | /logout | | 激活注销的路径。 | -| post_logout_redirect_uri | string | False | | | `logout_path` 收到注销请求后将用户重定向到的 URL。| -| redirect_uri | string | False | | | 通过 OpenID 提供商进行身份验证后重定向到的 URI。请注意,重定向 URI 不应与请求 URI 相同,而应为请求 URI 的子路径。例如,如果路由的 `uri` 是 `/api/v1/*`,则 `redirect_uri` 可以配置为 `/api/v1/redirect`。如果未配置 `redirect_uri`,APISIX 将在请求 URI 后附加 `/.apisix/redirect` 以确定 `redirect_uri` 的值。| -| timeout | integer | False | 3 | [1,...] | 请求超时时间(秒)。| -| ssl_verify | boolean | False | false | | 如果为 true,则验证 OpenID 提供商的 SSL 证书。| -| introspection_endpoint | string | False | | |用于自检访问令牌的 OpenID 提供程序的 [令牌自检](https://datatracker.ietf.org/doc/html/rfc7662) 端点的 URL。如果未设置,则将使用众所周知的发现文档中提供的自检端点[作为后备](https://github.com/zmartzone/lua-resty-openidc/commit/cdaf824996d2b499de4c72852c91733872137c9c)。| -| introspection_endpoint_auth_method | string | False | client_secret_basic | | 令牌自检端点的身份验证方法。该值应为 `introspection_endpoint_auth_methods_supported` [授权服务器元数据](https://www.rfc-editor.org/rfc/rfc8414.html) 中指定的身份验证方法之一,如众所周知的发现文档中所示,例如 `client_secret_basic`、`client_secret_post`、`private_key_jwt` 和 `client_secret_jwt`。| -| token_endpoint_auth_method | string | False | client_secret_basic | | 令牌端点的身份验证方法。该值应为 `token_endpoint_auth_methods_supported` [授权服务器元数据](https://www.rfc-editor.org/rfc/rfc8414.html) 中指定的身份验证方法之一,如众所周知的发现文档中所示,例如 `client_secret_basic`、`client_secret_post`、`private_key_jwt` 和 `client_secret_jwt`。如果配置的方法不受支持,则回退到 `token_endpoint_auth_methods_supported` 数组中的第一个方法。| -| public_key | string | False | | | 用于验证 JWT 签名 id 的公钥使用非对称算法。提供此值来执行令牌验证将跳过客户端凭据流中的令牌自检。您可以以 `-----BEGIN PUBLIC KEY-----\\n……\\n-----END PUBLIC KEY-----` 格式传递公钥。| -| use_jwks | boolean | False | false | | 如果为 true 并且未设置 `public_key`,则使用 JWKS 验证 JWT 签名并跳过客户端凭据流中的令牌自检。JWKS 端点是从发现文档中解析出来的。| -| use_pkce | boolean | False | false | | 如果为 true,则使用 [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) 中定义的授权码流的代码交换证明密钥 (PKCE)。| -| token_signing_alg_values_expected | string | False | | | 用于签署 JWT 的算法,例如 `RS256`。 | -| set_access_token_header | boolean | False | true | | 如果为 true,则在请求标头中设置访问令牌。默认情况下,使用 `X-Access-Token` 标头。| -| access_token_in_authorization_header | boolean | False | false | | 如果为 true 并且 `set_access_token_header` 也为 true,则在 `Authorization` 标头中设置访问令牌。 | -| set_id_token_header | boolean | False | true | | 如果为 true 并且 ID 令牌可用,则在 `X-ID-Token` 请求标头中设置值。 | -| set_userinfo_header | boolean | False | true | | 如果为 true 并且用户信息数据可用,则在 `X-Userinfo` 请求标头中设置值。 | -| set_refresh_token_header | boolean | False | false | | 如果为 true 并且刷新令牌可用,则在 `X-Refresh-Token` 请求标头中设置值。 | -| session | object | False | | | 当 `bearer_only` 为 `false` 且插件使用 Authorization Code 流程时使用的 Session 配置。 | -| session.secret | string | True | | 16 个字符以上 | 当 `bearer_only` 为 `false` 时,用于 session 加密和 HMAC 运算的密钥,若未配置则自动生成并保存到 etcd。当在独立模式下使用 APISIX 时,etcd 不再是配置中心,需要配置 `secret`。 | -| session.cookie | object | False | | | Cookie 配置。 | -| session.cookie.lifetime | integer | False | 3600 | | Cookie 生存时间(秒)。| -| unauth_action | string | False | auth | ["auth","deny","pass"] | 未经身份验证的请求的操作。设置为 `auth` 时,重定向到 OpenID 提供程序的身份验证端点。设置为 `pass` 时,允许请求而无需身份验证。设置为 `deny` 时,返回 401 未经身份验证的响应,而不是启动授权代码授予流程。| -| proxy_opts | object | False | | | OpenID 提供程序背后的代理服务器的配置。| -| proxy_opts.http_proxy | string | False | | | HTTP 请求的代理服务器地址,例如 `http://:`。| -| proxy_opts.https_proxy | string | False | | | HTTPS 请求的代理服务器地址,例如 `http://:`。 | -| proxy_opts.http_proxy_authorization | string | False | | Basic [base64 用户名:密码] | 与 `http_proxy` 一起使用的默认 `Proxy-Authorization` 标头值。可以用自定义的 `Proxy-Authorization` 请求标头覆盖。 | -| proxy_opts.https_proxy_authorization | string | False | | Basic [base64 用户名:密码] | 与 `https_proxy` 一起使用的默认 `Proxy-Authorization` 标头值。不能用自定义的 `Proxy-Authorization` 请求标头覆盖,因为使用 HTTPS 时,授权在连接时完成。 | -| proxy_opts.no_proxy | string | False | | | 不应代理的主机的逗号分隔列表。| -| authorization_params | object | False | | | 在请求中发送到授权端点的附加参数。 | -| client_rsa_private_key | string | False | | | 用于签署 JWT 以向 OP 进行身份验证的客户端 RSA 私钥。当 `token_endpoint_auth_method` 为 `private_key_jwt` 时必需。 | -| client_rsa_private_key_id | string | False | | | 用于计算签名的 JWT 的客户端 RSA 私钥 ID。当 `token_endpoint_auth_method` 为 `private_key_jwt` 时可选。 | -| client_jwt_assertion_expires_in | integer | False | 60 | | 用于向 OP 进行身份验证的签名 JWT 的生命周期,以秒为单位。当 `token_endpoint_auth_method` 为 `private_key_jwt` 或 `client_secret_jwt` 时使用。 | -| renew_access_token_on_expiry | boolean | False | true | | 如果为 true,则在访问令牌过期或刷新令牌可用时尝试静默更新访问令牌。如果令牌无法更新,则重定向用户进行重新身份验证。| -| access_token_expires_in | integer | False | | | 如果令牌端点响应中不存在 `expires_in` 属性,则访问令牌的有效期(以秒为单位)。 | -| refresh_session_interval | integer | False | | | 刷新用户 ID 令牌而无需重新认证的时间间隔。如果未设置,则不会检查网关向客户端发出的会话的到期时间。如果设置为 900,则表示在 900 秒后刷新用户的 `id_token`(或浏览器中的会话),而无需重新认证。 | -| iat_slack | integer | False | 120 | | ID 令牌中 `iat` 声明的时钟偏差容忍度(以秒为单位)。 | -| accept_none_alg | boolean | False | false | | 如果 OpenID 提供程序未签署其 ID 令牌(例如当签名算法设置为`none` 时),则设置为 true。 | -| accept_unsupported_alg | boolean | False | true | | 如果为 true,则忽略 ID 令牌签名以接受不支持的签名算法。 | -| access_token_expires_leeway | integer | False | 0 | | 访问令牌续订的过期余地(以秒为单位)。当设置为大于 0 的值时,令牌续订将在令牌过期前设定的时间内进行。这样可以避免访问令牌在到达资源服务器时刚好过期而导致的错误。| -| force_reauthorize | boolean | False | false | | 如果为 true,即使令牌已被缓存,也执行授权流程。 | -| use_nonce | boolean | False | false | | 如果为 true,在授权请求中启用 nonce 参数。 | -| revoke_tokens_on_logout | boolean | False | false | | 如果为 true,则通知授权服务器,撤销端点不再需要先前获得的刷新或访问令牌。 | -| jwk_expires_in | integer | False | 86400 | | JWK 缓存的过期时间(秒)。 | -| jwt_verification_cache_ignore | boolean | False | false | | 如果为 true,则强制重新验证承载令牌并忽略任何现有的缓存验证结果。 | -| cache_segment | string | False | | | 缓存段的可选名称,用于分隔和区分令牌自检或 JWT 验证使用的缓存。| -| introspection_interval | integer | False | 0 | | 缓存和自省访问令牌的 TTL(以秒为单位)。默认值为 0,这意味着不使用此选项,插件默认使用 `introspection_expiry_claim` 中定义的到期声明传递的 TTL。如果`introspection_interval` 大于 0 且小于 `introspection_expiry_claim` 中定义的到期声明传递的 TTL,则使用`introspection_interval`。| -| introspection_expiry_claim | string | False | exp | | 到期声明的名称,它控制缓存和自省访问令牌的 TTL。| -| introspection_addon_headers | array[string] | False | | | 用于将其他标头值附加到自省 HTTP 请求。如果原始请求中不存在指定的标头,则不会附加值。| +| discovery | string | 是 | | | OpenID 提供商的知名发现文档的 URL,其中包含 OP API 端点列表。插件可以直接利用发现文档中的端点。您也可以单独配置这些端点,这优先于发现文档中提供的端点。 | +| scope | string | 否 | openid | | 与应返回的有关经过身份验证的用户的信息相对应的 OIDC 范围,也称为 [claim](https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims)。这用于向用户授权适当的权限。默认值为 `openid`,这是 OIDC 返回唯一标识经过身份验证的用户的 `sub` 声明所需的范围。可以附加其他范围并用空格分隔,例如 `openid email profile`。 | +| required_scopes | array[string] | 否 | | | 访问令牌中必须存在的范围。当 `bearer_only` 为 `true` 时与自省端点结合使用。如果缺少任何必需的范围,插件将以 403 禁止错误拒绝请求。| +| realm | string | 否 | apisix | | 由于持有者令牌无效,[`WWW-Authenticate`](https://www.rfc-editor.org/rfc/rfc6750#section-3) 响应标头中的领域伴随 401 未经授权的请求。 | +| bearer_only | boolean | 否 | false | | 如果为 true,则严格要求在身份验证请求中使用持有者访问令牌。 | +| logout_path | string | 否 | /logout | | 激活注销的路径。 | +| post_logout_redirect_uri | string | 否 | | | `logout_path` 收到注销请求后将用户重定向到的 URL。| +| redirect_uri | string | 否 | | | 通过 OpenID 提供商进行身份验证后重定向到的 URI。请注意,重定向 URI 不应与请求 URI 相同,而应为请求 URI 的子路径。例如,如果路由的 `uri` 是 `/api/v1/*`,则 `redirect_uri` 可以配置为 `/api/v1/redirect`。如果未配置 `redirect_uri`,APISIX 将在请求 URI 后附加 `/.apisix/redirect` 以确定 `redirect_uri` 的值。| +| timeout | integer | 否 | 3 | [1,...] | 请求超时时间(秒)。| +| ssl_verify | boolean | 否 | false | | 如果为 true,则验证 OpenID 提供商的 SSL 证书。| +| introspection_endpoint | string | 否 | | |用于自检访问令牌的 OpenID 提供程序的 [令牌自检](https://datatracker.ietf.org/doc/html/rfc7662) 端点的 URL。如果未设置,则将使用众所周知的发现文档中提供的自检端点[作为后备](https://github.com/zmartzone/lua-resty-openidc/commit/cdaf824996d2b499de4c72852c91733872137c9c)。| +| introspection_endpoint_auth_method | string | 否 | client_secret_basic | | 令牌自检端点的身份验证方法。该值应为 `introspection_endpoint_auth_methods_supported` [授权服务器元数据](https://www.rfc-editor.org/rfc/rfc8414.html) 中指定的身份验证方法之一,如众所周知的发现文档中所示,例如 `client_secret_basic`、`client_secret_post`、`private_key_jwt` 和 `client_secret_jwt`。| +| token_endpoint_auth_method | string | 否 | client_secret_basic | | 令牌端点的身份验证方法。该值应为 `token_endpoint_auth_methods_supported` [授权服务器元数据](https://www.rfc-editor.org/rfc/rfc8414.html) 中指定的身份验证方法之一,如众所周知的发现文档中所示,例如 `client_secret_basic`、`client_secret_post`、`private_key_jwt` 和 `client_secret_jwt`。如果配置的方法不受支持,则回退到 `token_endpoint_auth_methods_supported` 数组中的第一个方法。| +| public_key | string | 否 | | | 用于验证 JWT 签名 id 的公钥使用非对称算法。提供此值来执行令牌验证将跳过客户端凭据流中的令牌自检。您可以以 `-----BEGIN PUBLIC KEY-----\\n……\\n-----END PUBLIC KEY-----` 格式传递公钥。| +| use_jwks | boolean | 否 | false | | 如果为 true 并且未设置 `public_key`,则使用 JWKS 验证 JWT 签名并跳过客户端凭据流中的令牌自检。JWKS 端点是从发现文档中解析出来的。| +| use_pkce | boolean | 否 | false | | 如果为 true,则使用 [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) 中定义的授权码流的代码交换证明密钥 (PKCE)。| +| token_signing_alg_values_expected | string | 否 | | | 用于签署 JWT 的算法,例如 `RS256`。 | +| set_access_token_header | boolean | 否 | true | | 如果为 true,则在请求标头中设置访问令牌。默认情况下,使用 `X-Access-Token` 标头。| +| access_token_in_authorization_header | boolean | 否 | false | | 如果为 true 并且 `set_access_token_header` 也为 true,则在 `Authorization` 标头中设置访问令牌。 | +| set_id_token_header | boolean | 否 | true | | 如果为 true 并且 ID 令牌可用,则在 `X-ID-Token` 请求标头中设置值。 | +| set_userinfo_header | boolean | 否 | true | | 如果为 true 并且用户信息数据可用,则在 `X-Userinfo` 请求标头中设置值。 | +| set_refresh_token_header | boolean | 否 | false | | 如果为 true 并且刷新令牌可用,则在 `X-Refresh-Token` 请求标头中设置值。 | +| session | object | 否 | | | 当 `bearer_only` 为 `false` 且插件使用 Authorization Code 流程时使用的 Session 配置。 | +| session.secret | string | 是 | | 16 个字符以上 | 当 `bearer_only` 为 `false` 时,用于 session 加密和 HMAC 运算的密钥,若未配置则自动生成并保存到 etcd。当在独立模式下使用 APISIX 时,etcd 不再是配置中心,需要配置 `secret`。 | +| session.cookie | object | 否 | | | Cookie 配置。 | +| session.cookie.lifetime | integer | 否 | 3600 | | Cookie 生存时间(秒)。| +| unauth_action | string | 否 | auth | ["auth","deny","pass"] | 未经身份验证的请求的操作。设置为 `auth` 时,重定向到 OpenID 提供程序的身份验证端点。设置为 `pass` 时,允许请求而无需身份验证。设置为 `deny` 时,返回 401 未经身份验证的响应,而不是启动授权代码授予流程。| +| proxy_opts | object | 否 | | | OpenID 提供程序背后的代理服务器的配置。| +| proxy_opts.http_proxy | string | 否 | | | HTTP 请求的代理服务器地址,例如 `http://:`。| +| proxy_opts.https_proxy | string | 否 | | | HTTPS 请求的代理服务器地址,例如 `http://:`。 | +| proxy_opts.http_proxy_authorization | string | 否 | | Basic [base64 用户名:密码] | 与 `http_proxy` 一起使用的默认 `Proxy-Authorization` 标头值。可以用自定义的 `Proxy-Authorization` 请求标头覆盖。 | +| proxy_opts.https_proxy_authorization | string | 否 | | Basic [base64 用户名:密码] | 与 `https_proxy` 一起使用的默认 `Proxy-Authorization` 标头值。不能用自定义的 `Proxy-Authorization` 请求标头覆盖,因为使用 HTTPS 时,授权在连接时完成。 | +| proxy_opts.no_proxy | string | 否 | | | 不应代理的主机的逗号分隔列表。| +| authorization_params | object | 否 | | | 在请求中发送到授权端点的附加参数。 | +| client_rsa_private_key | string | 否 | | | 用于签署 JWT 以向 OP 进行身份验证的客户端 RSA 私钥。当 `token_endpoint_auth_method` 为 `private_key_jwt` 时必需。 | +| client_rsa_private_key_id | string | 否 | | | 用于计算签名的 JWT 的客户端 RSA 私钥 ID。当 `token_endpoint_auth_method` 为 `private_key_jwt` 时可选。 | +| client_jwt_assertion_expires_in | integer | 否 | 60 | | 用于向 OP 进行身份验证的签名 JWT 的生命周期,以秒为单位。当 `token_endpoint_auth_method` 为 `private_key_jwt` 或 `client_secret_jwt` 时使用。 | +| renew_access_token_on_expiry | boolean | 否 | true | | 如果为 true,则在访问令牌过期或刷新令牌可用时尝试静默更新访问令牌。如果令牌无法更新,则重定向用户进行重新身份验证。| +| access_token_expires_in | integer | 否 | | | 如果令牌端点响应中不存在 `expires_in` 属性,则访问令牌的有效期(以秒为单位)。 | +| refresh_session_interval | integer | 否 | | | 刷新用户 ID 令牌而无需重新认证的时间间隔。如果未设置,则不会检查网关向客户端发出的会话的到期时间。如果设置为 900,则表示在 900 秒后刷新用户的 `id_token`(或浏览器中的会话),而无需重新认证。 | +| iat_slack | integer | 否 | 120 | | ID 令牌中 `iat` 声明的时钟偏差容忍度(以秒为单位)。 | +| accept_none_alg | boolean | 否 | false | | 如果 OpenID 提供程序未签署其 ID 令牌(例如当签名算法设置为`none` 时),则设置为 true。 | +| accept_unsupported_alg | boolean | 否 | true | | 如果为 true,则忽略 ID 令牌签名以接受不支持的签名算法。 | +| access_token_expires_leeway | integer | 否 | 0 | | 访问令牌续订的过期余地(以秒为单位)。当设置为大于 0 的值时,令牌续订将在令牌过期前设定的时间内进行。这样可以避免访问令牌在到达资源服务器时刚好过期而导致的错误。| +| force_reauthorize | boolean | 否 | false | | 如果为 true,即使令牌已被缓存,也执行授权流程。 | +| use_nonce | boolean | 否 | false | | 如果为 true,在授权请求中启用 nonce 参数。 | +| revoke_tokens_on_logout | boolean | 否 | false | | 如果为 true,则通知授权服务器,撤销端点不再需要先前获得的刷新或访问令牌。 | +| jwk_expires_in | integer | 否 | 86400 | | JWK 缓存的过期时间(秒)。 | +| jwt_verification_cache_ignore | boolean | 否 | false | | 如果为 true,则强制重新验证承载令牌并忽略任何现有的缓存验证结果。 | +| cache_segment | string | 否 | | | 缓存段的可选名称,用于分隔和区分令牌自检或 JWT 验证使用的缓存。| +| introspection_interval | integer | 否 | 0 | | 缓存和自省访问令牌的 TTL(以秒为单位)。默认值为 0,这意味着不使用此选项,插件默认使用 `introspection_expiry_claim` 中定义的到期声明传递的 TTL。如果`introspection_interval` 大于 0 且小于 `introspection_expiry_claim` 中定义的到期声明传递的 TTL,则使用`introspection_interval`。| +| introspection_expiry_claim | string | 否 | exp | | 到期声明的名称,它控制缓存和自省访问令牌的 TTL。| +| introspection_addon_headers | array[string] | 否 | | | 用于将其他标头值附加到自省 HTTP 请求。如果原始请求中不存在指定的标头,则不会附加值。| 注意:schema 中还定义了 `encrypt_fields = {"client_secret"}`,这意味着该字段将会被加密存储在 etcd 中。具体参考 [加密存储字段](../plugin-develop.md#加密存储字段)。