diff --git a/plugins/wasm-go/extensions/oidc/README.md b/plugins/wasm-go/extensions/oidc/README.md
index 0eccaa866..d967b2997 100644
--- a/plugins/wasm-go/extensions/oidc/README.md
+++ b/plugins/wasm-go/extensions/oidc/README.md
@@ -1,58 +1,222 @@
-# 功能说明
-`oidc` 本插件实现了 OIDC 认证能力, 插件目前存在的 CSRF 攻击问题,不建议用于生产环境
+# OIDC Wasm 插件
-# 配置字段
-| 字段 | 数据类型 | 填写要求 | 默认值 | 描述 |
-|-------------------|--------|------|------------|------------------------------------------------------------------|
-| issuer | string | 必填 | - | 设置认证服务的 issuer ,即签发人。 |
-| client_id | string | 必填 | - | 输入服务注册的应用 ID 。 |
-| client_secret | string | 必填 | - | 输入服务注册的应用 Secret 。 |
-| redirect_url | string | 必填 | - | 输入授权成功后的重定向地址,需要与 OIDC 中配置的重定向地址保持一致。该地址的后缀需为 (oauth2/callback)。 |
-| client_url | string | 必填 | - | 登陆成功跳转后的地址,如果未跳转成功,请检查设置的 cookiename 是否重复。 |
-| scopes | Array | 必填 | - | 输入授权作用域的数组。 |
-| skip_expiry_check | bool | 选填 | false | 控制是否检测 IDToken 的过期状态。 |
-| skip_nonce_check | bool | 选填 | true | 控制是否检测 Nonce 值。 |
-| timeout_millis | int | 选填 | 500 | 设置请求与认证服务连接的超时时长。如果频繁遇到超时错误,建议增加该时长。 |
-| cookie_name | string | 选填 | "_oidc_wasm" | 设置 cookie 的名称, 如果一个域名下多个路由设置不同的认证服务,建议设置不同名称。 |
-| cookie_domain | string | 必填 | - | 设置 cookie 的域名。 |
-| cookie_path | string | 选填 | "/" | 设置 cookie 的存储路径。 |
-| cookie_secure | bool | 选填 | false | 控制 cookie 是否只在 HTTPS 下传输。 |
-| cookie_httponly | bool | 选填 | true | 控制 cookie 是否仅限于 HTTP 传输,禁止JavaScript访问。 |
-| cookie_samesite | string | 选填 | "Lax" | 设置 cookie 的 SameSite 属性,如:"Lax", "none"。第三方跳转一般建议默认设置为Lax |
-| service_source | string | 必填 | - | 类型为固定 ip 或者 DNS ,输入认证 oidc 服务的注册来源。 |
-| service_name | string | 必填 | - | 输入认证 oidc 服务的注册名称。 |
-| service_port | int | 必填 | - | 输入认证 oidc 服务的服务端口。 |
-| service_host | string | 必填 | - | 当类型为固定ip时必须填写,输入认证 oidc 服务的主机名。 |
-| service_domain | string | 必填 | - | 当类型为DNS时必须填写,输入认证 oidc 服务的domain。 |
+## 简介
-这是一个用于OIDC认证配置的表格,确保在提供所有必要的信息时遵循上述指导。
-# 配置示例
-- 固定ip
-```yaml
-issuer: "http://127.0.0.1:9090/realms/myrealm"
-redirect_url: "http://foo.bar.com/bar/oauth2/callback"
-client_url: "http://foo.bar.com/"
-scopes:
- - "openid"
- - "email"
-cookie_name: "_oauth2_wasm_keyclocak"
-cookie_domain: "foo.bar.com"
-client_id: "xxxxxxxxxxxx"
-client_secret: "xxxxxxxxxxxxxx"
-service_host: "127.0.0.1:9090"
-service_name: "keyclocak"
-service_port: 80
-service_source: "ip"
+本仓库提供了一个高度可集成的Wasm插件支持OpenID Connect(OIDC)身份认证,同时强化了对跨站请求伪造(CSRF)攻击的防御能力,支持OpenID Connect协议中的Logout Endpoint。在通过Wasm插件OIDC验证后的请求会携带 `Authorization`的标头对应Access Token。
+
+### OIDC 流程图
+
+
+
+
+
+### OIDC 流程解析
+
+1. 模拟用户访问对应服务api
+
+ ```shell
+ curl --url "foo.bar.com/headers"
+ ```
+
+2. Higress重定向到OIDC Provider登录页同时携带client_id、response_type、scope等OIDC认证的参数并设置csrf cookie防御CSRF攻击
+
+ ```shell
+ curl --url "https://dev-o43xb1mz7ya7ach4.us.auth0.com/authorize"\
+ --url-query "approval_prompt=force" \
+ --url-query "client_id=YagFqRD9tfNIaac5BamjhsSatjrAnsnZ" \
+ --url-query "redirect_uri=http%3A%2F%2Ffoo.bar.com%2Foauth2%2Fcallback" \
+ --url-query "response_type=code" \
+ --url-query "scope=openid+email+offline_access" \
+ --url-query "state=nT06xdCqn4IqemzBRV5hmO73U_hCjskrH_VupPqdcdw%3A%2Ffoo" \
+ --header "Set-Cookie: _oauth2_proxy_csrf=LPruATEDgcdmelr8zScD_ObhsbP4zSzvcgmPlcNDcJpFJ0OvhxP2hFotsU-kZnYxd5KsIjzeIXGTOjf8TKcbTHbDIt-aQoZORXI_0id3qeY0Jt78223DPeJ1xBqa8VO0UiEOUFOR53FGxirJOdKFxaAvxDFb1Ok=|1718962455|V1QGWyjQ4hMNOQ4Jtf17HeQJdVqHdt5d65uraFduMIU=; Path=/; Expires=Fri, 21 Jun 2024 08:06:20 GMT; HttpOnly"
+ ```
+
+3. 用户在登录页进行登录
+
+
+
+4. 携带授权重定向到Higress并携带了state参数用于验证CSRF Cookie,授权code用于交换Token
+
+ ```shell
+ curl --url "http://foo.bar.com/oauth2/callback" \
+ --url-query "state=nT06xdCqn4IqemzBRV5hmO73U_hCjskrH_VupPqdcdw%3A%2Ffoo" \
+ --url-query "code=0bdopoS2c2lx95u7iO0OH9kY1TvaEdJHo4lB6CT2_qVFm"
+ ```
+
+5. 利用授权交换id_token和access_token
+
+ ```shell
+ curl -X POST \
+ --url "https://dev-o43xb1mz7ya7ach4.us.auth0.com/oauth/token" \
+ --data "grant_type=authorization_code" \
+ --data "client_id=YagFqRD9tfNIaac5BamjhsSatjrAnsnZ" \
+ --data "client_secret=ekqv5XoZuMFtYms1NszEqRx03qct6BPvGeJUeptNG4y09PrY16BKT9IWezTrrhJJ" \
+ --data "redirect_uri=http%3A%2F%2Ffoo.bar.com%2Foauth2%2Fcallback" \
+ --data "code=0bdopoS2c2lx95u7iO0OH9kY1TvaEdJHo4lB6CT2_qVFm" \
+ ```
+
+ 返回的请求里包含了id_token, access_token,refresh_token用于后续刷新access_token
+
+ ```json
+ {
+ "access_token": "eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiaXNzIjoiaHR0cHM6Ly9kZXYtbzQzeGIxbXo3eWE3YWNoNC51cy5hdXRoMC5jb20vIn0..WP_WRVM-y3fM1sN4.fAQqtKoKZNG9Wj0OhtrMgtsjTJ2J72M2klDRd9SvUKGbiYsZNPmIl_qJUf81D3VIjD59o9xrOOJIzXTgsfFVA2x15g-jBlNh68N7dyhXu9237Tbplweu1jA25IZDSnjitQ3pbf7xJVIfPnWcrzl6uT8G1EP-omFcl6AQprV2FoKFMCGFCgeafuttppKe1a8mpJDj7AFLPs-344tT9mvCWmI4DuoLFh0PiqMMJBByoijRSxcSdXLPxZng84j8JVF7H6mFa-dj-icP-KLy6yvzEaRKz_uwBzQCzgYK434LIpqw_PRuN3ClEsenwRgIsNdVjvKcoAysfoZhmRy9BQaE0I7qTohSBFNX6A.mgGGeeWgugfXcUcsX4T5dQ",
+ "refresh_token": "GrZ1f2JvzjAZQzSXmyr1ScWbv8aMFBvzAXHBUSiILcDEG",
+ "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imc1Z1ExSF9ZbTY0WUlvVkQwSVpXTCJ9.eyJlbWFpbCI6IjE2MDExNTYyNjhAcXEuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJpc3MiOiJodHRwczovL2Rldi1vNDN4YjFtejd5YTdhY2g0LnVzLmF1dGgwLmNvbS8iLCJhdWQiOiJZYWdGcVJEOXRmTklhYWM1QmFtamhzU2F0anJBbnNuWiIsImlhdCI6MTcxOTE5ODYzOCwiZXhwIjoxNzE5MjM0NjM4LCJzdWIiOiJhdXRoMHw2NjVkNzFlNzRjMTMxMTc3YmU2NmU2MDciLCJzaWQiOiJjdDJVOF9ZUS16VDdFOGkwRTNNeUstejc5ZGlWUWhhVSJ9.gfzXKJ0FeqzYqOUDLQHWcUG19IOLqkpLN09xTmIat0umrlGV5VNSumgWH3XJmmwnhdb8AThH3Jf-7kbRJzu4rM-BbGbFTRBTzNHeUajFOFrIgld5VENQ_M_sXHkTp0psWKSr9vF24kmilCfSbvC5lBKjt878ljZ7-xteWuaUYOMUdcJb4DSv0-zjX01sonJxYamTlhji3M4TAW7VwhwqyZt8dBhVSNaRw1wUKj-M1JrBDLyx65sroZtSqVA0udIrqMHEbWYb2de7JjzlqG003HRMzwOm7OXgEd5ZVFqgmBLosgixOU5DJ4A26nlqK92Sp6VqDMRvA-3ym8W_m-wJ_A",
+ "scope": "openid email offline_access",
+ "expires_in": 86400,
+ "token_type": "Bearer"
+ }
+ ```
+
+6. 将获得的id_token和access_token加密存储在Cookie _oauth2_proxy中,用于后续用户登录状态的验证,同时清除Cookie _oauth2_proxy_csrf
+
+ ```json
+ "Set-Cookie": [
+ "_oauth2_proxy_csrf=; Path=/; Expires=Mon, 24 Jun 2024 02:17:39 GMT; HttpOnly",
+ "_oauth2_proxy=8zM_Pcfpp_gesKFe4SMg08o5Iv0A8WAOQOmG1-vZBbQ56UggYVC0Cu-gFMEoxJZU5q1O5vqRlVBizlLetgVjRCksGVbttwl8tQ7h5YiyIubbbtvF1T4JzLh3QfzUUrwbB-VznOkh8qLbjAhddocecjBt4rMiDyceKXqMr4eO5TUEMx4vHtJYnTYalMeTYhGXk5MNSyrdZX9NnQnkdrCjiOQM13ggwob2nYwhGWaAlgzFSWkgkdtBy2Cl_YMWZ8_gKk9rDX289-JrJyGpr5k9O9RzRhZoY2iE3Mcr8-Q37RTji1Ga22QO-XkAcSaGqY1Qo7jLdmgZTYKC5JvtdLc4rj3vcbveYxU7R3Pt2vEribQjKTh4Sqb0aA03p4cxXyZN4SUfBW1NAOm4JLPUhKJy8frqC9_E0nVqPvpvnacaoQs8WkX2zp75xHoMa3SD6KZhQ5JUiPEiNkOaUsyafLvht6lLkNDhgzW3BP2czoe0DCDBLnsot0jH-qQpMZYkaGr-ZnRKI1OPl1vHls3mao5juOAW1VB2A9aughgc8SJ55IFZpMfFMdHdTDdMqPODkItX2PK44GX-pHeLxkOqrzp3GHtMInpL5QIQlTuux3erm3CG-ntlUE7JBtN2T9LEb8XfIFu58X9_vzMun4JQlje2Thi9_taI_z1DSaTtvNNb54wJfSPwYCCl4OsH-BacVmPQhH6TTZ6gP2Qsm5TR2o1U2D9fuVkSM-OPCG9l3tILambIQwC3vofMW6X8SIFSmhJUDvN7NbwxowBiZ6Y7GJRZlAk_GKDkpsdrdIvC67QqczZFphRVnm6qi-gPO41APCbcO6fgTwyOhbP3RrZZKWSIqWJYhNE3_Sfkf0565H7sC7Hc8XUUjJvP3WnjKS9x7KwzWa-dsUjV3-Q-VNl-rXTguVNAIirYK-qrMNMZGCRcJqcLnUF0V_J2lVmFyVsSlE3t0sDw2xmbkOwDptXFOjQL5Rb4esUMYdCBWFajBfvUtcZEFtYhD0kb6VcbjXO3NCVW5qKh_l9C9SRCc7TG1vcRAqUQlRXHacTGWfcWsuQkCJ3Mp_oWaDxs1GRDykQYxAn5sTICovThWEU2C6o75grWaNrkj5NU-0eHh3ryvxLmGLBOXZV9OQhtKShWmUgywSWMxOHOuZAqdAPULc8KheuGFjXYp-RnCbFYWePJmwzfQw89kSkj1KUZgMYwKEjSz62z2qc9KLczomv76ortQzvo4Hv9kaW6xVuQj5R5Oq6_WMBOqsmUMzcXpxCIOGjcdcZRBc0Fm09Uy9oV1PRqvAE4PGtfyrCaoqILBix8UIww63B07YGwzQ-hAXDysBK-Vca2x7GmGdXsNXXcTgu00bdsjtHZPDBBWGfL3g_rMAXr2vWyvK4CwNjcaPAmrlF3geHPwbIePT0hskBboX1v1bsuhzsai7rGM4r53pnb1ZEoTQDa1B-HyokFgo14XiwME0zE1ifpNzefjpkz1YY2krJlqfCydNwoKaTit4tD2yHlnxAeFF9iIrxzSKErNUFpmyLa7ge7V33vhEH-6k5oBTLE2Q2BrC6aAkLCcPwU9xv_SzBDQPRY0MEYv3kGF03Swo1crRbGh-aifYX9NiHDsmG6r1vAnx0MAOw2Jzuz2x6SSdfBrzlcoWBlrwiZzd9kAKq75n1Uy9uzZ8SRnkBrEZySHBwEbu196VklkRE0jqwC-e3wWNNuviSOfwkVeX-7QdOoO10yw9VK2sW52lFvIEf4chv_ta7bGfAZOWBjpktG6ZLD81SE6A88zpqG2SysSyNMp9hl-umG-5sFsjCn_c9E8bDvwkUOUVb9bNqhBDsZgR0BNPawiOZjmyfhzmwmWf-zgFzfFSV6BvOwNRi3sCOHTsWcuk9NBQ_YK8CpNkVl3WeIBSDfidimuC_QV9UWKs1GPk35ZRkM4zKtLY2JsBFWKaDy_P80TcOzcMBoP8gIBClXZ-WUqfE8s1yyc4jrq-qL1_wJ24ef1O9FktsbyZiDKXw2vnqsT8-g_hCeG-unrT1ZFscf8oNdqczARHX-K4vKH2k3uIqEx1M=|1719199056|2rsgdUIClHNEpxBLlHOVRYup6e4oKensQfljtmn4B80=; Path=/; Expires=Mon, 01 Jul 2024 03:17:36 GMT; HttpOnly"
+ ]
+ ```
+
+7. 携带 Authorization的标头对应access_token访问对应api
+
+ ```shell
+ curl --url "foo.bar.com/headers"
+ --header "Authorization: Bearer eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiaXNzIjoiaHR0cHM6Ly9kZXYtbzQzeGIxbXo3eWE3YWNoNC51cy5hdXRoMC5jb20vIn0..WP_WRVM-y3fM1sN4.fAQqtKoKZNG9Wj0OhtrMgtsjTJ2J72M2klDRd9SvUKGbiYsZNPmIl_qJUf81D3VIjD59o9xrOOJIzXTgsfFVA2x15g-jBlNh68N7dyhXu9237Tbplweu1jA25IZDSnjitQ3pbf7xJVIfPnWcrzl6uT8G1EP-omFcl6AQprV2FoKFMCGFCgeafuttppKe1a8mpJDj7AFLPs-344tT9mvCWmI4DuoLFh0PiqMMJBByoijRSxcSdXLPxZng84j8JVF7H6mFa-dj-icP-KLy6yvzEaRKz_uwBzQCzgYK434LIpqw_PRuN3ClEsenwRgIsNdVjvKcoAysfoZhmRy9BQaE0I7qTohSBFNX6A.mgGGeeWgugfXcUcsX4T5dQ"
+ ```
+
+8. 后端服务根据access_token获取用户信息并返回对应的Http响应
+
+ ```json
+ {
+ "email": "******",
+ "email_verified": false,
+ "iss": "https://dev-o43xb1mz7ya7ach4.us.auth0.com/",
+ "aud": "YagFqRD9tfNIaac5BamjhsSatjrAnsnZ",
+ "iat": 1719198638,
+ "exp": 1719234638,
+ "sub": "auth0|665d71e74c131177be66e607",
+ "sid": "ct2U8_YQ-zT7E8i0E3MyK-z79diVQhaU"
+ }
+ ```
+
+## 配置
+
+### 配置项
+
+| Option | Type | Description | Default |
+| ----------------------------- | ------------ | ------------------------------------------------------------ | ----------------- |
+| cookie_name | string | the name of the cookie that the oauth_proxy creates. Should be changed to use a [cookie prefix](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#cookie_prefixes) (`__Host-` or `__Secure-`) if `--cookie-secure` is set. | `"_oauth2_proxy"` |
+| cookie_secret | string | the seed string for secure cookies (optionally base64 encoded) | |
+| cookie_domains | string\|list | Optional cookie domains to force cookies to (e.g. `.yourcompany.com`). The longest domain matching the request's host will be used (or the shortest cookie domain if there is no match). | |
+| cookie_path | string | an optional cookie path to force cookies to (e.g. `/poc/`) | `"/"` |
+| cookie_expire | duration | expire timeframe for cookie. If set to 0, cookie becomes a session-cookie which will expire when the browser is closed. | 168h0m0s |
+| cookie_refresh | duration | refresh the cookie after this duration; `0` to disable | |
+| cookie_secure | bool | set [secure (HTTPS only) cookie flag](https://owasp.org/www-community/controls/SecureFlag) | true |
+| cookie_httponly | bool | set HttpOnly cookie flag | true |
+| cookie_samesite | string | set SameSite cookie attribute (`"lax"`, `"strict"`, `"none"`, or `""`). | `""` |
+| cookie_csrf_per_request | bool | Enable having different CSRF cookies per request, making it possible to have parallel requests. | false |
+| cookie_csrf_expire | duration | expire timeframe for CSRF cookie | 15m |
+| client_id | string | the OAuth Client ID | |
+| client_secret | string | the OAuth Client Secret | |
+| provider | string | OAuth provider | oidc |
+| pass_authorization_header | bool | pass OIDC IDToken to upstream via Authorization Bearer header | true |
+| oidc_issuer_url | string | the OpenID Connect issuer URL, e.g. `"https://dev-o43xb1mz7ya7ach4.us.auth0.com"` | |
+| oidc_verifier_request_timeout | uint32 | OIDC verifier discovery request timeout | 2000(ms) |
+| scope | string | OAuth scope specification | |
+| redirect_url | string | the OAuth Redirect URL, e.g. `"https://internalapp.yourcompany.com/oauth2/callback"` | |
+| service_name | string | registered name of the OIDC service, e.g. `auth.dns`, `keycloak.static` | |
+| service_port | int64 | service port of the OIDC service | |
+| service_host | string | host of the OIDC service when type is static ip | |
+| match_type | string | match type (`whitelist` or `blacklist`) | `"whitelist"` |
+| match_list | rule\|list | a list of (match_rule_domain, match_rule_path, and match_rule_type). | |
+| match_rule_domain | string | match rule domain, support wildcard pattern such as `*.bar.com` | |
+| match_rule_path | string | match rule path such as `/headers` | |
+| match_rule_type | string | match rule type can be `exact` or `prefix` or `regex` | |
+
+### 生成 Cookie 密钥
+
+``` python
+python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())'
```
-- DNS域名
-- 在服务来源中注册好服务后,创建对应的ingress
+
+参考:[Oauth2-proxy Generating a Cookie Secret](https://oauth2-proxy.github.io/oauth2-proxy/configuration/overview#generating-a-cookie-secret)
+
+### 黑白名单模式
+
+支持黑白名单模式配置,默认为白名单模式,白名单为空,即所有请求都需要经过验证,匹配域名支持泛域名例如`*.bar.com`,匹配规则支持精确匹配`exact`,前缀匹配`prefix`,正则匹配`regex`
+
+* **白名单模式**
+
+```yaml
+match_type: 'whitelist'
+match_list:
+ - match_rule_domain: '*.bar.com'
+ match_rule_path: '/foo'
+ match_rule_type: 'prefix'
+```
+
+泛域名`*.bar.com`下前缀匹配`/foo`的请求无需验证
+
+* **黑名单模式**
+
+```yaml
+match_type: 'blacklist'
+match_list:
+ - match_rule_domain: '*.bar.com'
+ match_rule_path: '/headers'
+ match_rule_type: 'prefix'
+```
+
+只有泛域名`*.bar.com`下前缀匹配`/header`的请求需要验证
+
+### 注销用户
+
+注销用户需重定向到`/oauth2/sign_out`这个端点。这个端点仅移除oauth2-proxy自己设置的cookie,也就是说,用户仍然在OIDC Provider处保持登录状态,并且在再次访问应用时可能会自动重新登录。因此还需要使用`rd`查询参数将用户重定向到认证提供商的注销页面,即重定向用户到类似如下地址(注意URL编码!):
+
+```
+/oauth2/sign_out?rd=https%3A%2F%2Fmy-oidc-provider.example.com%2Fsign_out_page
+```
+
+或者,可以在`X-Auth-Request-Redirect`头部中包含重定向URL:
+
+```
+GET /oauth2/sign_out HTTP/1.1
+X-Auth-Request-Redirect: https://my-oidc-provider.example.com/sign_out_page
+...
+```
+
+重定向URL中可以包含`post_logout_redirect_uri`参数指定OIDC Provider登出后跳转到的页面,例如后端服务的登出页面,不携带该参数则默认跳转到OIDC Provider的登出页面,详情见下方auth0和keycloak的示例(如果OIDC Provider支持会话管理和发现,那么"sign_out_page"应该是从[metadata](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig)中获取的`end_session_endpoint`)
+
+### OIDC 服务 HTTPS 协议
+
+如果 OIDC Provider 为 HTTPS 协议,参考Higress中[配置后端服务协议:HTTPS](https://higress.io/docs/latest/user/annotation-use-case/#%E9%85%8D%E7%BD%AE%E5%90%8E%E7%AB%AF%E6%9C%8D%E5%8A%A1%E5%8D%8F%E8%AE%AEhttps%E6%88%96grpc)的说明需要通过使用注解`higress.io/backend-protocol: "HTTPS"`配置请求转发至后端服务使用HTTPS协议,参考Auth0示例中Ingress配置
+
+## 配置示例
+
+### Auth0 配置示例
+
+#### Step 1: 配置 Auth0 账户
+
+- 登录到开发人员 Okta 网站 [Developer Auth0 site](https://auth0.com/)
+- 注册测试 web 应用程序
+
+**注**:需填写Allowed Callback URLs, Allowed Logout URLs, Allowed Web Origins等配置项,否则 OIDC Provider 会认为用户跳转的重定向 URL 或登出 URL 无效
+
+#### Step 2: Higress 配置服务来源
+
+* 在Higress服务来源中创建auth0 DNS来源
+
+
+
+#### Step 3: OIDC 服务 HTTPS 配置
+
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
- name: example-ingress
+ name: auth0-ingress
annotations:
- higress.io/destination: okta.dns
+ higress.io/destination: auth.dns
higress.io/backend-protocol: "HTTPS"
higress.io/ignore-path-case: "false"
spec:
@@ -68,29 +232,152 @@ spec:
apiGroup: networking.higress.io
kind: McpBridge
name: default
-
```
-- 创建wasm插件
+
+#### Step 4: Wasm 插件配置
+
```yaml
-issuer: "https://dev-65874123.okta.com"
-redirect_url: "http://foo.bar.com/a/oauth2/callback"
-scopes:
- - "openid"
- - "email"
-client_url: "http://foo.bar.com/a"
-cookie_domain: "foo.bar.com"
-client_id: "xxxxxxxxxxxxxxx"
-client_secret: "xxxxxxx"
-service_domain: "dev-65874123.okta.com"
-service_name: "okta"
+redirect_url: 'http://foo.bar.com/oauth2/callback'
+oidc_issuer_url: 'https://dev-o43xb1mz7ya7ach4.us.auth0.com/'
+client_id: 'XXXXXXXXXXXXXXXX'
+client_secret: 'XXXXXXXXXXXXXXXX'
+scope: 'openid email offline_access'
+cookie_secret: 'nqavJrGvRmQxWwGNptLdyUVKcBNZ2b18Guc1n_8DCfY='
+service_name: 'auth.dns'
service_port: 443
-service_source: "dns"
-timeout_millis: 2000
+match_type: 'whitelist'
+match_list:
+ - match_rule_domain: '*.bar.com'
+ match_rule_path: '/foo'
+ match_rule_type: 'prefix'
```
-在通过插件验证后会携带 `Authorization`的标头携带令牌
+**注**:必须先配置服务来源,wasm插件在初始化时需要访问配置的服务获取openid-configuration
+#### 访问服务页面,未登陆的话进行跳转
+
+#### 登陆成功跳转到服务页面
+headers中可以看到携带了_oauth2_proxy 的cookie用于下次登陆访问,Authorization对应IDToken用于后端服务获得用户信息
+
+
+#### 访问登出跳转到登出页面
+
+```
+http://foo.bar.com/oauth2/sign_out?rd=https%3A%2F%2Fdev-o43xb1mz7ya7ach4.us.auth0.com%2Foidc%2Flogout
+```
+
+
+
+#### 访问登出跳转到登出页面(携带post_logout_redirect_uri参数跳转指定uri)
+
+```
+http://foo.bar.com/oauth2/sign_out?rd=https%3A%2F%2Fdev-o43xb1mz7ya7ach4.us.auth0.com%2Foidc%2Flogout%3Fpost_logout_redirect_uri%3Dhttp%3A%2F%2Ffoo.bar.com%2Ffoo
+```
+
+注:post_logout_redirect_uri跳转的uri需要在OIDC Provider Allowed URLs处配置才可以正常跳转
+
+
+
+### keycloak 配置示例
+
+#### Step 1: Get started with keycloak on docker
+
+
+
+**注**:需填写Valid redirect URIs, Valid post logout URIs, Web origins配置项,否则 OIDC Provider 会认为用户跳转的重定向 URL 或登出 URL 无效
+
+#### Step 2: Higress 配置服务来源
+
+* 在Higress服务来源中创建Keycloak固定地址服务
+
+
+
+#### Step 3: Wasm 插件配置
+
+```yaml
+redirect_url: 'http://foo.bar.com/oauth2/callback'
+oidc_issuer_url: 'http://127.0.0.1:9090/realms/myrealm'
+client_id: 'XXXXXXXXXXXXXXXX'
+client_secret: 'XXXXXXXXXXXXXXXX'
+scope: 'openid email'
+cookie_secret: 'nqavJrGvRmQxWwGNptLdyUVKcBNZ2b18Guc1n_8DCfY='
+service_name: 'keycloak.static'
+service_port: 80
+service_host: '127.0.0.1:9090'
+match_type: 'blacklist'
+match_list:
+ - match_rule_domain: '*.bar.com'
+ match_rule_path: '/headers'
+ match_rule_type: 'prefix'
+```
+
+#### 访问服务页面,未登陆的话进行跳转
+
+
+
+#### 登陆成功跳转到服务页面
+
+
+
+#### 访问登出跳转到登出页面
+
+```
+http://foo.bar.com/oauth2/sign_out?rd=http%3A%2F%2F127.0.0.1:9090%2Frealms%2Fmyrealm%2Fprotocol%2Fopenid-connect%2Flogout
+```
+
+
+
+#### 访问登出跳转到登出页面(携带post_logout_redirect_uri参数跳转指定uri)
+
+```
+http://foo.bar.com/oauth2/sign_out?rd=http%3A%2F%2F127.0.0.1:9090%2Frealms%2Fmyrealm%2Fprotocol%2Fopenid-connect%2Flogout%3Fpost_logout_redirect_uri%3Dhttp%3A%2F%2Ffoo.bar.com%2Ffoo
+```
+
+
+
+### Aliyun 配置示例
+
+#### Step 1: 配置 Aliyun OAuth应用
+
+参考[Web应用登录阿里云](https://help.aliyun.com/zh/ram/user-guide/access-alibaba-cloud-apis-from-a-web-application)流程配置 OAuth 应用
+
+#### Step 2: Higress 配置服务来源
+
+* 在Higress服务来源中创建Aliyun DNS服务
+
+
+
+#### Step 3: Wasm 插件配置
+
+```yaml
+redirect_url: 'http://foo.bar.com/oauth2/callback'
+provider: aliyun
+oidc_issuer_url: 'https://oauth.aliyun.com/'
+client_id: 'XXXXXXXXXXXXXXXX'
+client_secret: 'XXXXXXXXXXXXXXXX'
+scope: 'openid'
+cookie_secret: 'nqavJrGvRmQxWwGNptLdyUVKcBNZ2b18Guc1n_8DCfY='
+service_name: 'aliyun.dns'
+service_port: 443
+match_type: whitelist
+match_list:
+ - match_rule_domain: 'foo.bar.com'
+ match_rule_path: /foo
+ match_rule_type: prefix
+```
+
+#### 访问服务页面,未登陆的话进行跳转
+
+
+
+直接使用RAM用户登录或者点击主账户登录
+
+
+
+#### 登陆成功跳转到服务页面
+
+
\ No newline at end of file
diff --git a/plugins/wasm-go/extensions/oidc/VERSION b/plugins/wasm-go/extensions/oidc/VERSION
deleted file mode 100644
index afaf360d3..000000000
--- a/plugins/wasm-go/extensions/oidc/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-1.0.0
\ No newline at end of file
diff --git a/plugins/wasm-go/extensions/oidc/doc/Oidc.md b/plugins/wasm-go/extensions/oidc/doc/Oidc.md
deleted file mode 100644
index 0d9c399d5..000000000
--- a/plugins/wasm-go/extensions/oidc/doc/Oidc.md
+++ /dev/null
@@ -1,199 +0,0 @@
-
-
-# OpenID Connect
-
-本文介绍了一个示例 OpenID Connect 插件配置,用于使用与 Okta , Auth0 , keycloak 身份提供程序对浏览器客户端进行身份验证。
-
-## OpenID Connect with Okta
-### 配置 okta 账户
-* 登录到开发人员 Okta 网站 [Developer Okta site](https://developer.okta.com/)
-* 注册测试 web 应用程序
-
-### 将测试 okta 应用程序与 Higress 关联
-* 创建服务来源
-
-* 查看服务列表,有即成功
-
-### 将测试 okta 应用程序与您的 Oidc-Wasm 插件关联
-* 创建访问 okta 的 ingress
-```yaml
-apiVersion: networking.k8s.io/v1
-kind: Ingress
-metadata:
- name: example-ingress
- annotations:
- higress.io/destination: okta.dns
- higress.io/backend-protocol: "HTTPS"
- higress.io/ignore-path-case: "false"
-spec:
- ingressClassName: higress
- rules:
- - host: foo.bar.com
- http:
- paths:
- - path: /
- pathType: Prefix
- backend:
- resource:
- apiGroup: networking.higress.io
- kind: McpBridge
- name: default
-
-```
-* 配置 oidc 插件
-```yaml
-issuer: "https://dev-65874123.okta.com"
-redirect_url: "http://foo.bar.com/a/oauth2/callback"
-scopes:
- - "openid"
- - "email"
-client_url: "http://foo.bar.com/a"
-cookie_domain: "foo.bar.com"
-client_id: "xxxx"
-client_secret: "xxxxx"
-service_domain: "dev-65874123.okta.com"
-service_name: "okta"
-service_port: 443
-service_source: "dns"
-timeout_millis: 2000
-```
-### 访问服务页面,未登陆的话进行跳转
-
-### 登陆成功跳转到服务页面
-
-
-
-
-
----
-
-## OpenID Connect with auth0
-### 配置 auth0 账户
-* 登录到开发人员 Okta 网站 [Developer Auth0 site](https://auth0.com/)
-* 注册测试 web 应用程序
-
-### 将测试 auth0 应用程序与 Higress 关联
-* 创建服务来源
-
-
-### 将测试 auth0 应用程序与您的 Oidc-Wasm 插件关联
-* 创建访问 auth0 的 ingress
-```yaml
-apiVersion: networking.k8s.io/v1
-kind: Ingress
-metadata:
- name: example-ingress
- annotations:
- higress.io/destination: auth.dns
- higress.io/backend-protocol: "HTTPS"
- higress.io/ignore-path-case: "false"
-spec:
- ingressClassName: higress
- rules:
- - host: foo.bar.com
- http:
- paths:
- - path: /
- pathType: Prefix
- backend:
- resource:
- apiGroup: networking.higress.io
- kind: McpBridge
- name: default
-
-
-```
-* 配置 oidc 插件
-```yaml
-CookieName: "_oauth2_wasm_c"
-client_id: "xxxxx"
-client_secret: "xxxxxx"
-cookie_domain: "foo.bar.com"
-cookie_path: "/b"
-client_url: "http://foo.bar.com/b"
-service_domain: "dev-650jsqsvuyrk4ahg.us.auth0.com"
-issuer: "https://dev-650jsqsvuyrk4ahg.us.auth0.com/"
-redirect_url: "http://foo.bar.com/b/oauth2/callback"
-scopes:
- - "openid"
- - "email"
-service_name: "auth"
-service_port: 443
-service_source: "dns"
-timeout_millis: 2000
-```
-
-
-
-### 访问服务页面,未登陆的话进行跳转
-
-
-### 登陆成功跳转到服务页面
-
-
----
-
-
-
-## OpenID Connect with keyclocak
-### 配置 keyclocak 账户
-* 本文档采用 docker 本机进行部署,所以注册的 ip 应该采用 ifconfig 获取网卡 ip
-
-
-* 注册测试 web 应用程序
-
-### 将测试 keyclocak 应用程序与 Higress 关联
-* 创建服务来源
-
-
-### 将测试 keyclocak 应用程序与您的 Oidc-Wasm 插件关联
-* 配置 oidc 插件
-```yaml
-issuer: "http://127.0.0.1:9090/realms/myrealm"
-redirect_url: "http://foo.bar.com/bar/oauth2/callback"
-client_url: "http://foo.bar.com/"
-scopes:
- - "openid"
- - "email"
-cookie_name: "_oauth2_wasm_keyclocak"
-cookie_domain: "foo.bar.com"
-client_id: "myclinet"
-client_secret: "EdKdKBX4N0jtYuPD4aGxZWiI7EVh4pr9"
-service_host: "127.0.0.1:9090"
-service_name: "keyclocak"
-service_port: 80
-service_source: "ip"
-```
-
-
-### 访问服务页面,未登陆的话进行跳转
-
-### 登陆成功跳转到服务页面
-
-
-
-## 与oauth2-proxy支持的服务对比
-| 服务 | 是否支持 | |
-| ----------------------- | ----------------- | ---------------------------------------- |
-| Auth0 | 支持 | |
-| Okta | 支持 | |
-| dex | 支持 | |
-| Keycloak | 支持 | |
-| Gitea | 支持 | |
-| GitLab | 支持 | |
-| Google | 不支持 | 域名不一致 |
-| GitHub | 不支持 | 域名不一致 |
-| Microsoft Azure AD | 不支持 | |
-| Azure | 不支持 | |
-
-
-
-
-
-
-## 主要的差异
-| 主要功能差异 | OAuth2-Proxy | OIDC-Wasm |
-|----------------------------------------------------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
-| 将服务放置在 OAuth2-Proxy 后 | ✓ | 不具备直接验证的能力 |
-| 在当前层可以展示具体信息,如 email 等 | ✓ | 作为网关的插件,校验 token 的正确性后只是进行了转发。在实现的过程中已经捕捉到了 ID Token 信息,可以实现提取出具体的信息用于优化日志展示等。 |
-| 校验一些非标准的 issuer,如启动 skipIssuerCheck 的 Github | ✓ | 已经抽象出 OIDCHandler,开启 skipIssuerChecker。只要实现 OIDCHandler 的能力,可以在 ProcessRedirect 中指定 authURL 的校验,在 ProcessExchangeToken 中指定 TokenURL 获取 token,在 ProcessVerify 中校验 token |
\ No newline at end of file
diff --git a/plugins/wasm-go/extensions/oidc/doc/auth0_0.png b/plugins/wasm-go/extensions/oidc/doc/auth0_0.png
deleted file mode 100644
index 1af35a98b..000000000
Binary files a/plugins/wasm-go/extensions/oidc/doc/auth0_0.png and /dev/null differ
diff --git a/plugins/wasm-go/extensions/oidc/doc/auth0_1.png b/plugins/wasm-go/extensions/oidc/doc/auth0_1.png
deleted file mode 100644
index a5048c270..000000000
Binary files a/plugins/wasm-go/extensions/oidc/doc/auth0_1.png and /dev/null differ
diff --git a/plugins/wasm-go/extensions/oidc/doc/keycloak_0.png b/plugins/wasm-go/extensions/oidc/doc/keycloak_0.png
deleted file mode 100644
index bbe11e362..000000000
Binary files a/plugins/wasm-go/extensions/oidc/doc/keycloak_0.png and /dev/null differ
diff --git a/plugins/wasm-go/extensions/oidc/doc/keycloak_1.png b/plugins/wasm-go/extensions/oidc/doc/keycloak_1.png
deleted file mode 100644
index 182869b06..000000000
Binary files a/plugins/wasm-go/extensions/oidc/doc/keycloak_1.png and /dev/null differ
diff --git a/plugins/wasm-go/extensions/oidc/doc/keycloak_2.png b/plugins/wasm-go/extensions/oidc/doc/keycloak_2.png
deleted file mode 100644
index 4e68d7c41..000000000
Binary files a/plugins/wasm-go/extensions/oidc/doc/keycloak_2.png and /dev/null differ
diff --git a/plugins/wasm-go/extensions/oidc/doc/keycloak_3.png b/plugins/wasm-go/extensions/oidc/doc/keycloak_3.png
deleted file mode 100644
index 8618bbff5..000000000
Binary files a/plugins/wasm-go/extensions/oidc/doc/keycloak_3.png and /dev/null differ
diff --git a/plugins/wasm-go/extensions/oidc/doc/oath_2.png b/plugins/wasm-go/extensions/oidc/doc/oath_2.png
deleted file mode 100644
index ee20e9fb4..000000000
Binary files a/plugins/wasm-go/extensions/oidc/doc/oath_2.png and /dev/null differ
diff --git a/plugins/wasm-go/extensions/oidc/doc/okta_1.png b/plugins/wasm-go/extensions/oidc/doc/okta_1.png
deleted file mode 100644
index dce85babc..000000000
Binary files a/plugins/wasm-go/extensions/oidc/doc/okta_1.png and /dev/null differ
diff --git a/plugins/wasm-go/extensions/oidc/doc/okta_2.png b/plugins/wasm-go/extensions/oidc/doc/okta_2.png
deleted file mode 100644
index 2adcb80f3..000000000
Binary files a/plugins/wasm-go/extensions/oidc/doc/okta_2.png and /dev/null differ
diff --git a/plugins/wasm-go/extensions/oidc/doc/okta_3.png b/plugins/wasm-go/extensions/oidc/doc/okta_3.png
deleted file mode 100644
index 2141f1879..000000000
Binary files a/plugins/wasm-go/extensions/oidc/doc/okta_3.png and /dev/null differ
diff --git a/plugins/wasm-go/extensions/oidc/doc/okta_4.png b/plugins/wasm-go/extensions/oidc/doc/okta_4.png
deleted file mode 100644
index d21abe276..000000000
Binary files a/plugins/wasm-go/extensions/oidc/doc/okta_4.png and /dev/null differ
diff --git a/plugins/wasm-go/extensions/oidc/go.mod b/plugins/wasm-go/extensions/oidc/go.mod
index 04262f31c..753be9a2d 100644
--- a/plugins/wasm-go/extensions/oidc/go.mod
+++ b/plugins/wasm-go/extensions/oidc/go.mod
@@ -1,27 +1,33 @@
-module oidc
+module github.com/alibaba/higress/plugins/wasm-go/extensions/oidc
go 1.19
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require (
- github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230807053545-d307d0e755f1
- github.com/go-jose/go-jose/v3 v3.0.0
+ github.com/alibaba/higress/plugins/wasm-go v1.3.6-0.20240531060402-2807ddfbb79e
+ github.com/higress-group/oauth2-proxy v1.0.0
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f
- github.com/tidwall/gjson v1.14.3
- golang.org/x/oauth2 v0.11.0
+ github.com/tidwall/gjson v1.17.1
)
require (
- github.com/golang/protobuf v1.5.3 // indirect
- github.com/google/uuid v1.3.0 // indirect
+ github.com/benbjohnson/clock v1.3.5 // indirect
+ github.com/bitly/go-simplejson v0.5.1 // indirect
+ github.com/go-jose/go-jose/v4 v4.0.1 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/gorilla/mux v1.8.1 // indirect
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect
- github.com/magefile/mage v1.14.0 // indirect
+ github.com/justinas/alice v1.2.0 // indirect
+ github.com/magefile/mage v1.15.1-0.20230912152418-9f54e0f83e2a // indirect
+ github.com/ohler55/ojg v1.22.0 // indirect
+ github.com/spf13/cast v1.6.0 // indirect
+ github.com/tetratelabs/wazero v1.7.2 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/resp v0.1.1 // indirect
- golang.org/x/crypto v0.12.0 // indirect
- golang.org/x/net v0.14.0 // indirect
- google.golang.org/appengine v1.6.7 // indirect
- google.golang.org/protobuf v1.31.0 // indirect
+ github.com/wasilibs/go-re2 v1.6.0 // indirect
+ golang.org/x/crypto v0.23.0 // indirect
+ golang.org/x/oauth2 v0.20.0 // indirect
+ golang.org/x/sys v0.21.0 // indirect
)
diff --git a/plugins/wasm-go/extensions/oidc/go.sum b/plugins/wasm-go/extensions/oidc/go.sum
index 320aff489..05210a07f 100644
--- a/plugins/wasm-go/extensions/oidc/go.sum
+++ b/plugins/wasm-go/extensions/oidc/go.sum
@@ -1,60 +1,52 @@
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
-github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
-github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
-github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
-github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
+github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
+github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
+github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
+github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
-github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
-github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
-github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
-github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
+github.com/higress-group/oauth2-proxy v1.0.0 h1:JwmApDYXhxwVcDD8HZ7fsOYq0rq2JxiYR7LszU5Uq9g=
+github.com/higress-group/oauth2-proxy v1.0.0/go.mod h1:UOXEF1DEkmLIfVO0p+gP5ceGPuWHI4IKMmQGt8aUTrw=
+github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f h1:ZIiIBRvIw62gA5MJhuwp1+2wWbqL9IGElQ499rUsYYg=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
-github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
-github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
-github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
+github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/magefile/mage v1.15.1-0.20230912152418-9f54e0f83e2a h1:tdPcGgyiH0K+SbsJBBm2oPyEIOTAvLBwD9TuUwVtZho=
+github.com/magefile/mage v1.15.1-0.20230912152418-9f54e0f83e2a/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
+github.com/ohler55/ojg v1.22.0 h1:McZObj3cD/Zz/ojzk5Pi5VvgQcagxmT1bVKNzhE5ihI=
+github.com/ohler55/ojg v1.22.0/go.mod h1:gQhDVpQLqrmnd2eqGAvJtn+NfKoYJbe/A4Sj3/Vro4o=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
+github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc=
+github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
+github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
+github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE=
github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
-golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
-golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
-golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
-golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
-google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
-google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+github.com/wasilibs/go-re2 v1.6.0 h1:CLlhDebt38wtl/zz4ww+hkXBMcxjrKFvTDXzFW2VOz8=
+github.com/wasilibs/go-re2 v1.6.0/go.mod h1:prArCyErsypRBI/jFAFJEbzyHzjABKqkzlidF0SNA04=
+github.com/wasilibs/nottinygc v0.4.0 h1:h1TJMihMC4neN6Zq+WKpLxgd9xCFMw7O9ETLwY2exJQ=
+golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
+golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
+golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
+golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/plugins/wasm-go/extensions/oidc/main.go b/plugins/wasm-go/extensions/oidc/main.go
index 331788181..c44b81006 100644
--- a/plugins/wasm-go/extensions/oidc/main.go
+++ b/plugins/wasm-go/extensions/oidc/main.go
@@ -1,261 +1,121 @@
-// Copyright (c) 2022 Alibaba Group Holding Ltd.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
package main
import (
- "errors"
"fmt"
"net/http"
"net/url"
- "oidc/oc"
"strings"
- "time"
+
+ oidc "github.com/higress-group/oauth2-proxy"
+ "github.com/higress-group/oauth2-proxy/pkg/apis/options"
+ "github.com/higress-group/oauth2-proxy/pkg/util"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
- "golang.org/x/oauth2"
)
-const OAUTH2CALLBACK = "oauth2/callback"
-
-type OidcConfig struct {
- Issuer string
- Path string
- ClientID string
- ClientSecret string
- RedirectURL string
- ClientURL string
- Timeout int
- CookieName string
- CookieSecret string
- CookieDomain string
- CookiePath string
- CookieSameSite string
- CookieSecure bool
- CookieHTTPOnly bool
- Scopes []string
- SkipExpiryCheck bool
- SkipNonceCheck bool
- Client wrapper.HttpClient
-}
-
func main() {
wrapper.SetCtx(
+ // 插件名称
"oidc",
+ // 为解析插件配置,设置自定义函数
wrapper.ParseConfigBy(parseConfig),
+ // 为处理请求头,设置自定义函数
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
+ // 为处理响应头,设置自定义函数
+ wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders),
)
}
-func parseConfig(json gjson.Result, config *OidcConfig, log wrapper.Log) error {
- config.Issuer = json.Get("issuer").String()
- if config.Issuer == "" {
- return errors.New("missing issuer in config")
- }
+var oidcHandler *oidc.OAuthProxy
- config.ClientID = json.Get("client_id").String()
- if config.ClientID == "" {
- return errors.New("missing client_id in config")
- }
+type PluginConfig struct {
+ options *options.Options
+}
- config.ClientSecret = json.Get("client_secret").String()
- if config.ClientSecret == "" {
- return errors.New("missing client_secret in config")
- }
- config.ClientURL = json.Get("client_url").String()
- _, err := url.ParseRequestURI(config.ClientURL)
- if err != nil {
- return errors.New("missing client_url in config or err format")
- }
-
- err = oc.IsValidRedirect(json.Get("redirect_url").String())
+// 在控制台插件配置中填写的yaml配置会自动转换为json,此处直接从json这个参数里解析配置即可
+func parseConfig(json gjson.Result, config *PluginConfig, log wrapper.Log) error {
+ oidc.SetLogger(log)
+ opts, err := oidc.LoadOptions(json)
if err != nil {
return err
}
- config.RedirectURL = json.Get("redirect_url").String()
+ config.options = opts
- config.SkipExpiryCheck = json.Get("skip_expiry_check").Bool()
- config.SkipNonceCheck = json.Get("skip_nonce_check").Bool()
- for _, item := range json.Get("scopes").Array() {
- scopes := item.String()
- config.Scopes = append(config.Scopes, scopes)
- }
- parsedURL, err := url.Parse(config.Issuer)
+ oidcHandler, err = oidc.NewOAuthProxy(opts)
if err != nil {
- return errors.New("failed to parse issuer URL")
- }
- config.Path = parsedURL.Path
-
- timeout := json.Get("timeout_millis").Int()
- if timeout <= 0 {
- config.Timeout = 500
- } else {
- config.Timeout = int(timeout)
+ return err
}
- //cookie
-
- config.CookieSecret = oc.Set32Bytes(config.ClientSecret)
- config.CookieName = json.Get("cookie_name").String()
- if config.CookieName == "" {
- config.CookieName = "_oidc_wasm"
- }
- config.CookieDomain = json.Get("cookie_domain").String()
- if config.CookieDomain == "" {
- return errors.New("missing cookie_domain in config or err format")
- }
- config.CookiePath = json.Get("cookie_path").String()
- if config.CookiePath == "" {
- config.CookiePath = "/"
- }
- config.CookieSecure = json.Get("cookie_secure").Bool()
- config.CookieSecure = json.Get("cookie_httponly").Bool()
-
- config.CookieSameSite = json.Get("cookie_samesite").String()
- if config.CookieSameSite == "" {
- config.CookieSameSite = "Lax"
- }
-
- serviceSource := json.Get("service_source").String()
- serviceName := json.Get("service_name").String()
- servicePort := json.Get("service_port").Int()
- serviceHost := json.Get("service_host").String()
- if serviceName == "" || servicePort == 0 {
- return errors.New("invalid service config")
- }
- switch serviceSource {
- case "ip":
- config.Client = wrapper.NewClusterClient(&wrapper.StaticIpCluster{
- ServiceName: serviceName,
- Host: serviceHost,
- Port: servicePort,
- })
- log.Debugf("%v %v %v", serviceName, serviceHost, servicePort)
- return nil
- case "dns":
- domain := json.Get("service_domain").String()
- if domain == "" {
- return errors.New("missing service_domain in config")
- }
- config.Client = wrapper.NewClusterClient(&wrapper.DnsCluster{
- ServiceName: serviceName,
- Port: servicePort,
- Domain: domain,
- })
- return nil
- default:
- return errors.New("unknown service source: " + serviceSource)
- }
+ wrapper.RegisteTickFunc(opts.VerifierInterval.Milliseconds(), func() {
+ oidcHandler.SetVerifier(opts)
+ })
+ return nil
}
-func onHttpRequestHeaders(ctx wrapper.HttpContext, config OidcConfig, log wrapper.Log) types.Action {
-
- defaultHandler := oc.NewDefaultOAuthHandler()
- cookieString, _ := proxywasm.GetHttpRequestHeader("cookie")
- oidcCookieValue, code, state, err := oc.GetParams(config.CookieName, cookieString, ctx.Path(), config.CookieSecret)
- if err != nil {
- oc.SendError(&log, fmt.Sprintf("GetParams err : %v", err), http.StatusBadRequest, "oidc.get_params_failed")
- return types.ActionContinue
- }
- nonce, _ := oc.Nonce(32)
- nonceStr := oc.GenState(nonce, config.ClientSecret, config.RedirectURL)
- createdAtTime := time.Now()
- cfg := &oc.Oatuh2Config{
- Config: oauth2.Config{
- ClientID: config.ClientID,
- ClientSecret: config.ClientSecret,
- RedirectURL: config.RedirectURL,
- Scopes: config.Scopes,
- },
- Issuer: config.Issuer,
- ClientUrl: config.ClientURL,
- Path: config.Path,
- SkipExpiryCheck: config.SkipExpiryCheck,
- Timeout: config.Timeout,
- Client: config.Client,
- SkipNonceCheck: config.SkipNonceCheck,
- Option: &oc.OidcOption{},
- CookieOption: &oc.CookieOption{
- Name: config.CookieName,
- Domain: config.CookieDomain,
- Secret: config.CookieSecret,
- Path: config.CookiePath,
- SameSite: config.CookieSameSite,
- Secure: config.CookieSecure,
- HTTPOnly: config.CookieHTTPOnly,
- },
- CookieData: &oc.CookieData{
- Nonce: []byte(nonceStr),
- CreatedAt: createdAtTime,
- },
- }
- log.Debugf("path :%v host :%v state :%v code :%v cookie :%v", ctx.Path(), ctx.Host(), state, code, oidcCookieValue)
-
- if oidcCookieValue == "" {
- if code == "" {
- if err := defaultHandler.ProcessRedirect(&log, cfg); err != nil {
- oc.SendError(&log, fmt.Sprintf("ProcessRedirect error : %v", err), http.StatusInternalServerError, "oidc.process_redirect_failed")
- return types.ActionContinue
- }
- return types.ActionPause
- }
- if strings.Contains(ctx.Path(), OAUTH2CALLBACK) {
- parts := strings.Split(state, ".")
- if len(parts) != 2 {
- oc.SendError(&log, "State signature verification failed", http.StatusUnauthorized, "oidc.bad_state")
- return types.ActionContinue
- }
- stateVal, signature := parts[0], parts[1]
- if err := oc.VerifyState(stateVal, signature, cfg.ClientSecret, cfg.RedirectURL); err != nil {
- oc.SendError(&log, fmt.Sprintf("State signature verification failed : %v", err), http.StatusUnauthorized, "oidc.invalid_state")
- return types.ActionContinue
- }
-
- cfg.Option.Code = code
- cfg.Option.Mod = oc.SenBack
- if err := defaultHandler.ProcessExchangeToken(&log, cfg); err != nil {
- oc.SendError(&log, fmt.Sprintf("ProcessExchangeToken error : %v", err), http.StatusInternalServerError, "oidc.process_exchange_token_failed")
- return types.ActionContinue
- }
- return types.ActionPause
- }
- oc.SendError(&log, fmt.Sprintf("redirect URL must end with oauth2/callback"), http.StatusBadRequest, "oidc.bad_redirect_url")
+func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrapper.Log) types.Action {
+ oidcHandler.SetContext(ctx)
+ req := getHttpRequest()
+ rw := util.NewRecorder()
+ if options.IsAllowedByMode(req.URL.Host, req.URL.Path, config.options.MatchRules, config.options.ProxyPrefix) {
+ log.Infof("request is allowed by mode %s", config.options.MatchRules.Mode)
return types.ActionContinue
}
- cookieData, err := oc.DeserializeCookieData(oidcCookieValue)
- if err != nil {
- oc.SendError(&log, fmt.Sprintf("DeserializeCookieData err : %v", err), http.StatusInternalServerError, "oidc.bad_cookie_value")
+ // TODO: remove this verifier after envoy support send request during parseConfig
+ if err := oidcHandler.ValidateVerifier(); err != nil {
+ log.Critical(err.Error())
return types.ActionContinue
}
- cfg.CookieData = &oc.CookieData{
- IDToken: cookieData.IDToken,
- Secret: cfg.CookieOption.Secret,
- Nonce: cookieData.Nonce,
- CreatedAt: cookieData.CreatedAt,
- ExpiresOn: cookieData.ExpiresOn,
- }
- cfg.Option.RawIdToken = cfg.CookieData.IDToken
- cfg.Option.Mod = oc.Access
- if err := defaultHandler.ProcessVerify(&log, cfg); err != nil {
- oc.SendError(&log, fmt.Sprintf("ProcessVerify error : %v", err), http.StatusUnauthorized, "oidc.unauthorized")
+ oidcHandler.ServeHTTP(rw, req)
+ if code := rw.GetStatus(); code != 0 {
return types.ActionContinue
}
-
return types.ActionPause
}
+
+func onHttpResponseHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrapper.Log) types.Action {
+ value := ctx.GetContext(oidc.SetCookieHeader)
+ if value != nil {
+ proxywasm.AddHttpResponseHeader(oidc.SetCookieHeader, value.(string))
+ }
+ oidcHandler.SetContext(nil)
+ return types.ActionContinue
+}
+
+func getHttpRequest() *http.Request {
+ headers, _ := proxywasm.GetHttpRequestHeaders()
+ var method, path, authority, scheme string
+ for _, header := range headers {
+ switch header[0] {
+ case ":method":
+ method = header[1]
+ case ":path":
+ path = header[1]
+ case ":authority":
+ authority = header[1]
+ case ":scheme":
+ scheme = header[1]
+ }
+ }
+ rawURL := fmt.Sprintf("%s://%s%s", scheme, authority, path)
+ parsedURL, _ := url.Parse(rawURL)
+
+ req := &http.Request{
+ Method: method,
+ URL: parsedURL,
+ Header: make(http.Header),
+ Body: nil,
+ }
+ req.Form, _ = url.ParseQuery(parsedURL.RawQuery)
+
+ for _, header := range headers {
+ if !strings.HasPrefix(header[0], ":") {
+ req.Header.Add(header[0], header[1])
+ }
+ }
+ return req
+}
diff --git a/plugins/wasm-go/extensions/oidc/oc/config.go b/plugins/wasm-go/extensions/oidc/oc/config.go
deleted file mode 100644
index b5c228ccf..000000000
--- a/plugins/wasm-go/extensions/oidc/oc/config.go
+++ /dev/null
@@ -1,116 +0,0 @@
-// Copyright (c) 2022 Alibaba Group Holding Ltd.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oc
-
-import (
- "errors"
- "net/url"
- "regexp"
- "strings"
- "time"
-
- "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
- "golang.org/x/oauth2"
-)
-
-type Accessmod int
-
-const (
- Access Accessmod = 0
- SenBack Accessmod = 1
-)
-
-var invalidRedirectRegex = regexp.MustCompile(`[/\\](?:[\s\v]*|\.{1,2})[/\\]`)
-
-type IDConfig struct {
- ClientID string
- SupportedSigningAlgs []string
- SkipExpiryCheck bool
- //SkipIssuerCheck 用于特殊情况,其中调用者希望推迟对签发者的验证。
- //当启用时,调用者必须独立验证令牌的签发者是否为已知的有效值。
- //
- //
- //不匹配的签发者通常指示客户端配置错误。如果不希望发生不匹配,请检查所提供的签发者URL是否正确,而不是启用这个选项。
- SkipIssuerCheck bool
- SkipNonceCheck bool
- Now func() time.Time
-}
-
-type idToken struct {
- Issuer string `json:"iss"`
- Subject string `json:"sub"`
- Audience audience `json:"aud"`
- Expiry jsonTime `json:"exp"`
- IssuedAt jsonTime `json:"iat"`
- NotBefore *jsonTime `json:"nbf"`
- Nonce string `json:"nonce"`
- AtHash string `json:"at_hash"`
- ClaimNames map[string]string `json:"_claim_names"`
- ClaimSources map[string]claimSource `json:"_claim_sources"`
-}
-
-type claimSource struct {
- Endpoint string `json:"endpoint"`
- AccessToken string `json:"access_token"`
-}
-
-type Oatuh2Config struct {
- oauth2.Config
- Issuer string
- JwksURL string
- ClientUrl string
- Path string
- SupportedSigningAlgs []string
- SkipExpiryCheck bool
- Timeout int
- Client wrapper.HttpClient
- SkipNonceCheck bool
-
- Option *OidcOption
- CookieOption *CookieOption
- CookieData *CookieData
-}
-
-type OidcOption struct {
- StateStr string
- Nonce string
- Code string
- Mod Accessmod
- RawIdToken string
- AuthStyle AuthStyle
-}
-
-func IsValidRedirect(redirect string) error {
- if !strings.HasSuffix(redirect, "oauth2/callback") {
- return errors.New("redirect URL must end with oauth2/callback")
- }
- switch {
- case redirect == "":
- return errors.New("redirect URL is empty")
- case strings.HasPrefix(redirect, "/"):
- if strings.HasPrefix(redirect, "//") || invalidRedirectRegex.MatchString(redirect) {
- return errors.New("invalid local redirect URL")
- }
- return nil
- case strings.HasPrefix(redirect, "http://"), strings.HasPrefix(redirect, "https://"):
- _, err := url.ParseRequestURI(redirect)
- if err != nil {
- return errors.New("invalid remote redirect URL")
- }
- return nil
- default:
- return errors.New("redirect URL must start with /, http://, or https://")
- }
-}
diff --git a/plugins/wasm-go/extensions/oidc/oc/cookie.go b/plugins/wasm-go/extensions/oidc/oc/cookie.go
deleted file mode 100644
index 08b80f49e..000000000
--- a/plugins/wasm-go/extensions/oidc/oc/cookie.go
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright (c) 2022 Alibaba Group Holding Ltd.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oc
-
-import (
- "crypto/aes"
- "crypto/cipher"
- "crypto/rand"
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io"
- "net/url"
- "strings"
- "time"
-)
-
-type CookieData struct {
- IDToken string
- Secret string
- Nonce []byte
- CreatedAt time.Time
- ExpiresOn time.Time
-}
-
-type CookieOption struct {
- Name string
- Domain string
- Secret string
- value string
- Path string
- SameSite string
- Expire time.Time
- Secure bool
- HTTPOnly bool
-}
-
-// SerializeAndEncrypt 将 CookieData 对象序列化并加密为一个安全的cookie header
-func SerializeAndEncryptCookieData(data *CookieData, keySecret string, cookieSettings *CookieOption) (string, error) {
- return buildSecureCookieHeader(data, keySecret, cookieSettings)
-}
-
-// DeserializeCookieData 将一个安全的cookie header解密并反序列化为 CookieData 对象
-func DeserializeCookieData(cookievalue string) (*CookieData, error) {
-
- data, err := retrieveCookieData(cookievalue)
- if err != nil {
- return nil, err
- }
- if checkCookieExpiry(data) {
- return nil, fmt.Errorf("cookie is expired")
- }
- return data, nil
-}
-func Set32Bytes(key string) string {
- const desiredLength = 32
- keyLength := len(key)
-
- var adjustedKey string
- if keyLength > desiredLength {
- adjustedKey = key[:desiredLength]
- } else if keyLength < desiredLength {
- padding := strings.Repeat("0", desiredLength-keyLength)
- adjustedKey = key + padding
- } else {
- adjustedKey = key
- }
- return adjustedKey
-}
-
-// 必须是16/24/32字节长
-func Decrypt(ciphertext string, key string) (string, error) {
- block, err := aes.NewCipher([]byte(key))
- if err != nil {
- return "", err
- }
-
- decodedCiphertext, err := base64.URLEncoding.DecodeString(ciphertext)
- if err != nil {
- return "", err
- }
-
- if len(decodedCiphertext) < aes.BlockSize {
- return "", fmt.Errorf("ciphertext is too short")
- }
-
- iv := decodedCiphertext[:aes.BlockSize]
- decodedCiphertext = decodedCiphertext[aes.BlockSize:]
-
- stream := cipher.NewCFBDecrypter(block, iv)
-
- stream.XORKeyStream(decodedCiphertext, decodedCiphertext)
-
- return string(decodedCiphertext), nil
-}
-
-func encrypt(plainText string, key string) (string, error) {
- block, err := aes.NewCipher([]byte(key))
- if err != nil {
- return "", err
- }
-
- ciphertext := make([]byte, aes.BlockSize+len(plainText))
- iv := ciphertext[:aes.BlockSize]
-
- if _, err := io.ReadFull(rand.Reader, iv); err != nil {
- return "", err
- }
-
- stream := cipher.NewCFBEncrypter(block, iv)
- stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(plainText))
-
- return base64.URLEncoding.EncodeToString(ciphertext), nil
-}
-
-func buildSecureCookieHeader(data *CookieData, keySecret string, cookieSettings *CookieOption) (string, error) {
- jsonData, err := json.Marshal(data)
- if err != nil {
- return "", err
- }
-
- encryptedValue, err := encrypt(string(jsonData), keySecret)
- if err != nil {
- return "", err
- }
-
- encodedValue := url.QueryEscape(encryptedValue)
- cookieSettings.value = encodedValue
-
- return generateCookie(cookieSettings), nil
-}
-
-func retrieveCookieData(cookieValue string) (*CookieData, error) {
- var data CookieData
- err := json.Unmarshal([]byte(cookieValue), &data)
- if err != nil {
- return nil, err
- }
-
- return &data, nil
-}
-
-func generateCookie(settings *CookieOption) string {
- var secureFlag, httpOnlyFlag, sameSiteFlag string
- if settings.Secure {
- secureFlag = "Secure;"
- }
-
- if settings.HTTPOnly {
- httpOnlyFlag = "HttpOnly;"
- }
-
- if settings.SameSite != "" {
- sameSiteFlag = fmt.Sprintf("SameSite=%s;", settings.SameSite)
- }
-
- expiresStr := settings.Expire.Format(time.RFC1123)
- maxAge := int(settings.Expire.Sub(time.Now()).Seconds())
-
- cookie := fmt.Sprintf("%s=%s; Path=%s; Domain=%s; Expires=%s; Max-Age=%d; %s %s %s",
- settings.Name,
- settings.value,
- settings.Path,
- settings.Domain,
- expiresStr,
- maxAge,
- secureFlag,
- httpOnlyFlag,
- sameSiteFlag,
- )
- return cookie
-}
-
-func checkCookieExpiry(data *CookieData) bool {
- return data.ExpiresOn.Before(time.Now())
-}
diff --git a/plugins/wasm-go/extensions/oidc/oc/encryption.go b/plugins/wasm-go/extensions/oidc/oc/encryption.go
deleted file mode 100644
index 9ffa77a97..000000000
--- a/plugins/wasm-go/extensions/oidc/oc/encryption.go
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright (c) 2022 Alibaba Group Holding Ltd.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oc
-
-import (
- "crypto/hmac"
- "crypto/rand"
- "crypto/sha256"
- "encoding/base64"
- "fmt"
- "golang.org/x/oauth2"
- "strings"
-)
-
-func Nonce(length int) ([]byte, error) {
- b := make([]byte, length)
- _, err := rand.Read(b)
- return b, err
-}
-
-func HashNonce(nonce []byte) string {
- hasher := sha256.New()
- hasher.Write(nonce)
- return base64.RawURLEncoding.EncodeToString(hasher.Sum(nil))
-}
-
-func GenState(nonce []byte, key string, redirectUrl string) string {
- hashedNonce := HashNonce(nonce)
- encodedRedirectUrl := base64.RawURLEncoding.EncodeToString([]byte(redirectUrl))
- state := fmt.Sprintf("%s:%s", hashedNonce, encodedRedirectUrl)
- signature := SignState(state, key)
- return fmt.Sprintf("%s.%s", state, signature)
-}
-
-func SignState(state string, key string) string {
- mac := hmac.New(sha256.New, []byte(key))
- mac.Write([]byte(state))
- return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
-}
-
-func VerifyState(state, signature, key, redirect string) error {
- if !hmac.Equal([]byte(signature), []byte(SignState(state, key))) {
- return fmt.Errorf("signature mismatch")
- }
-
- parts := strings.Split(state, ":")
- if len(parts) != 2 {
- return fmt.Errorf("invalid state format")
- }
-
- redirectUrl, err := base64.RawURLEncoding.DecodeString(parts[1])
- if err != nil {
- return fmt.Errorf("failed to decode redirect URL: %v", err)
- }
- if string(redirectUrl) != redirect {
- return fmt.Errorf("redirect URL mismatch")
- }
-
- return nil
-}
-
-func SetNonce(nonce string) oauth2.AuthCodeOption {
- return oauth2.SetAuthURLParam("nonce", nonce)
-}
diff --git a/plugins/wasm-go/extensions/oidc/oc/exchange.go b/plugins/wasm-go/extensions/oidc/oc/exchange.go
deleted file mode 100644
index c46d70d4a..000000000
--- a/plugins/wasm-go/extensions/oidc/oc/exchange.go
+++ /dev/null
@@ -1,256 +0,0 @@
-//Copyright 2023 go-oidc
-//
-//Licensed under the Apache License, Version 2.0 (the "License");
-//you may not use this file except in compliance with the License.
-//You may obtain a copy of the License at
-//
-//http://www.apache.org/licenses/LICENSE-2.0
-//
-//Unless required by applicable law or agreed to in writing, software
-//distributed under the License is distributed on an "AS IS" BASIS,
-//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-//See the License for the specific language governing permissions and
-//limitations under the License.
-
-package oc
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "github.com/tidwall/gjson"
- "io"
- "math"
- "mime"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "time"
-)
-
-type Token struct {
- AccessToken string
- TokenType string
- RefreshToken string
- Expiry time.Time
- Raw interface{}
-}
-
-type tokenJSON struct {
- AccessToken string `json:"access_token"`
- TokenType string `json:"token_type"`
- RefreshToken string `json:"refresh_token"`
- ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
-}
-
-type AuthCodeOption interface {
- setValue(url.Values)
-}
-
-type setParam struct{ k, v string }
-
-func (p setParam) SetValue(m url.Values) { m.Set(p.k, p.v) }
-
-func ReturnURL(RedirectURL, code string, opts ...AuthCodeOption) url.Values {
- v := url.Values{
- "grant_type": {"authorization_code"},
- "code": {code},
- }
- if RedirectURL != "" {
- v.Set("redirect_uri", RedirectURL)
- }
- for _, opt := range opts {
- opt.setValue(v)
- }
- return v
-}
-
-func TokenFromInternal(t *Token) *Token {
- if t == nil {
- return nil
- }
- return &Token{
- AccessToken: t.AccessToken,
- TokenType: t.TokenType,
- RefreshToken: t.RefreshToken,
- Expiry: t.Expiry,
- Raw: t.Raw,
- }
-}
-
-func (e *tokenJSON) expiry() (t time.Time) {
- if v := e.ExpiresIn; v != 0 {
- return time.Now().Add(time.Duration(v) * time.Second)
- }
- return
-}
-
-type expirationTime int32
-
-func (e *expirationTime) UnmarshalJSON(b []byte) error {
- if len(b) == 0 || string(b) == "null" {
- return nil
- }
- var n json.Number
- err := json.Unmarshal(b, &n)
- if err != nil {
- return err
- }
- i, err := n.Int64()
- if err != nil {
- return err
- }
- if i > math.MaxInt32 {
- i = math.MaxInt32
- }
- *e = expirationTime(i)
- return nil
-}
-
-type AuthStyle int
-
-const (
- AuthStyleUnknown AuthStyle = 0
- AuthStyleInParams AuthStyle = 1
- AuthStyleInHeader AuthStyle = 2
-)
-
-var authStyleCache struct {
- m map[string]AuthStyle // keyed by tokenURL
-}
-
-func LookupAuthStyle(tokenURL string) (style AuthStyle, ok bool) {
- style, ok = authStyleCache.m[tokenURL]
- return
-}
-
-// SetAuthStyle adds an entry to authStyleCache, documented above.
-func SetAuthStyle(tokenURL string, v AuthStyle) {
- if authStyleCache.m == nil {
- authStyleCache.m = make(map[string]AuthStyle)
- }
- authStyleCache.m[tokenURL] = v
-}
-
-func (t *Token) Extra(key string) interface{} {
- if raw, ok := t.Raw.(map[string]interface{}); ok {
- return raw[key]
- }
-
- vals, ok := t.Raw.(url.Values)
- if !ok {
- return nil
- }
-
- v := vals.Get(key)
- switch s := strings.TrimSpace(v); strings.Count(s, ".") {
- case 0: // Contains no "."; try to parse as int
- if i, err := strconv.ParseInt(s, 10, 64); err == nil {
- return i
- }
- case 1: // Contains a single "."; try to parse as float
- if f, err := strconv.ParseFloat(s, 64); err == nil {
- return f
- }
- }
-
- return v
-}
-
-func UnmarshalToken(token *Token, Headers http.Header, body []byte) (*Token, error) {
- if !gjson.ValidBytes(body) {
- return nil, fmt.Errorf("invalid JSON format in response body , get %v", string(body))
- }
- content, _, _ := mime.ParseMediaType(Headers.Get("Content-Type"))
-
- switch content {
- case "application/x-www-form-urlencoded", "text/plain":
- vals, err := url.ParseQuery(string(body))
- if err != nil {
- return nil, err
- }
- token = &Token{
- AccessToken: vals.Get("access_token"),
- TokenType: vals.Get("token_type"),
- RefreshToken: vals.Get("refresh_token"),
- Raw: vals,
- }
- e := vals.Get("expires_in")
- expires, _ := strconv.Atoi(e)
- if expires != 0 {
- token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
- }
- default:
- var tj tokenJSON
- if err := json.Unmarshal(body, &tj); err != nil {
- return nil, err
- }
- token = &Token{
- AccessToken: tj.AccessToken,
- TokenType: tj.TokenType,
- RefreshToken: tj.RefreshToken,
- Expiry: tj.expiry(),
- Raw: make(map[string]interface{}),
- }
- if err := json.Unmarshal(body, &token.Raw); err != nil {
- return nil, err
- }
-
- // no error checks for optional fields
- }
- if token.AccessToken == "" {
- return nil, errors.New("oauth2: server response missing access_token")
- }
- return token, nil
-}
-
-func NewTokenRequest(tokenURL, clientID, clientSecret string, v url.Values, authStyle AuthStyle) ([][2]string, []byte, error) {
- if authStyle == AuthStyleInParams {
- v = cloneURLValues(v)
- if clientID != "" {
- v.Set("client_id", clientID)
- }
- if clientSecret != "" {
- v.Set("client_secret", clientSecret)
- }
- }
- req, err := http.NewRequest("POST", tokenURL, strings.NewReader(v.Encode()))
- if err != nil {
- return nil, nil, err
- }
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- if authStyle == AuthStyleInHeader {
- req.SetBasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret))
- }
- var headerArray [][2]string
- for key, values := range req.Header {
- if len(values) > 0 {
- headerArray = append(headerArray, [2]string{key, values[0]})
- }
- }
- bodyBytes, err := io.ReadAll(req.Body)
- req.Body.Close()
- if err != nil {
- return nil, nil, err
- }
-
- return headerArray, bodyBytes, nil
-}
-
-func cloneURLValues(v url.Values) url.Values {
- v2 := make(url.Values, len(v))
- for k, vv := range v {
- v2[k] = append([]string(nil), vv...)
- }
- return v2
-}
-
-type RetrieveError struct {
- Response *http.Response
- Body []byte
-}
-
-func (r *RetrieveError) Error() string {
- return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body)
-}
diff --git a/plugins/wasm-go/extensions/oidc/oc/jwks.go b/plugins/wasm-go/extensions/oidc/oc/jwks.go
deleted file mode 100644
index a44a7fd64..000000000
--- a/plugins/wasm-go/extensions/oidc/oc/jwks.go
+++ /dev/null
@@ -1,628 +0,0 @@
-/*-
- * Copyright 2014 Square Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package oc
-
-import (
- "bytes"
- "crypto/ecdsa"
- "crypto/ed25519"
- "crypto/elliptic"
- "crypto/rsa"
- "crypto/sha1"
- "crypto/sha256"
- "crypto/x509"
- "encoding/base64"
- "encoding/hex"
- "errors"
- "fmt"
- "math/big"
- "net/url"
- "reflect"
- "strings"
-
- "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
- jose "github.com/go-jose/go-jose/v3"
- "github.com/tidwall/gjson"
-)
-
-const (
- RS256 = "RS256" // RSASSA-PKCS-v1.5 using SHA-256
- RS384 = "RS384" // RSASSA-PKCS-v1.5 using SHA-384
- RS512 = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
- ES256 = "ES256" // ECDSA using P-256 and SHA-256
- ES384 = "ES384" // ECDSA using P-384 and SHA-384
- ES512 = "ES512" // ECDSA using P-521 and SHA-512
- PS256 = "PS256" // RSASSA-PSS using SHA256 and MGF1-SHA256
- PS384 = "PS384" // RSASSA-PSS using SHA384 and MGF1-SHA384
- PS512 = "PS512" // RSASSA-PSS using SHA512 and MGF1-SHA512
- EdDSA = "EdDSA" // Ed25519 using SHA-512
-)
-
-var SupportedAlgorithms = map[string]bool{
- RS256: true,
- RS384: true,
- RS512: true,
- ES256: true,
- ES384: true,
- ES512: true,
- PS256: true,
- PS384: true,
- PS512: true,
- EdDSA: true,
-}
-
-type rawJSONWebKey struct {
- Use string `json:"use,omitempty"`
- Kty string `json:"kty,omitempty"`
- Kid string `json:"kid,omitempty"`
- Crv string `json:"crv,omitempty"`
- Alg string `json:"alg,omitempty"`
- K *byteBuffer `json:"k,omitempty"`
- X *byteBuffer `json:"x,omitempty"`
- Y *byteBuffer `json:"y,omitempty"`
- N *byteBuffer `json:"n,omitempty"`
- E *byteBuffer `json:"e,omitempty"`
- // -- Following fields are only used for private keys --
- // RSA uses D, P and Q, while ECDSA uses only D. Fields Dp, Dq, and Qi are
- // completely optional. Therefore for RSA/ECDSA, D != nil is a contract that
- // we have a private key whereas D == nil means we have only a public key.
- D *byteBuffer `json:"d,omitempty"`
- P *byteBuffer `json:"p,omitempty"`
- Q *byteBuffer `json:"q,omitempty"`
- Dp *byteBuffer `json:"dp,omitempty"`
- Dq *byteBuffer `json:"dq,omitempty"`
- Qi *byteBuffer `json:"qi,omitempty"`
- // Certificates
- X5c []string `json:"x5c,omitempty"`
- X5u string `json:"x5u,omitempty"`
- X5tSHA1 string `json:"x5t,omitempty"`
- X5tSHA256 string `json:"x5t#S256,omitempty"`
-}
-type JSONWebKey struct {
- // Cryptographic key, can be a symmetric or asymmetric key.
- Key interface{}
- // Key identifier, parsed from `kid` header.
- KeyID string
- // Key algorithm, parsed from `alg` header.
- Algorithm string
- // Key use, parsed from `use` header.
- Use string
-
- // X.509 certificate chain, parsed from `x5c` header.
- Certificates []*x509.Certificate
- // X.509 certificate URL, parsed from `x5u` header.
- CertificatesURL *url.URL
- // X.509 certificate thumbprint (SHA-1), parsed from `x5t` header.
- CertificateThumbprintSHA1 []byte
- // X.509 certificate thumbprint (SHA-256), parsed from `x5t#S256` header.
- CertificateThumbprintSHA256 []byte
-}
-type byteBuffer struct {
- data []byte
-}
-
-func base64URLDecode(value string) ([]byte, error) {
- value = strings.TrimRight(value, "=")
- return base64.RawURLEncoding.DecodeString(value)
-}
-
-func newBuffer(data []byte) *byteBuffer {
- if data == nil {
- return nil
- }
- return &byteBuffer{
- data: data,
- }
-}
-
-func parseCertificateChain(chain []string) ([]*x509.Certificate, error) {
-
- out := make([]*x509.Certificate, len(chain))
- for i, cert := range chain {
- raw, err := base64.StdEncoding.DecodeString(cert)
- if err != nil {
- var log wrapper.Log
- log.Errorf("base64.StdEncoding.DecodeString(cert) err :")
- return nil, err
- }
- out[i], err = x509.ParseCertificate(raw)
- if err != nil {
- return nil, err
- }
- }
- return out, nil
-}
-func (b byteBuffer) bigInt() *big.Int {
- return new(big.Int).SetBytes(b.data)
-}
-
-func (b byteBuffer) toInt() int {
- return int(b.bigInt().Int64())
-}
-
-func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
- var curve elliptic.Curve
- switch key.Crv {
- case "P-256":
- curve = elliptic.P256()
- case "P-384":
- curve = elliptic.P384()
- case "P-521":
- curve = elliptic.P521()
- default:
- return nil, fmt.Errorf("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv)
- }
-
- if key.X == nil || key.Y == nil {
- return nil, errors.New("go-jose/go-jose: invalid EC key, missing x/y values")
- }
-
- // The length of this octet string MUST be the full size of a coordinate for
- // the curve specified in the "crv" parameter.
- // https://tools.ietf.org/html/rfc7518#section-6.2.1.2
- if curveSize(curve) != len(key.X.data) {
- return nil, fmt.Errorf("go-jose/go-jose: invalid EC public key, wrong length for x")
- }
-
- if curveSize(curve) != len(key.Y.data) {
- return nil, fmt.Errorf("go-jose/go-jose: invalid EC public key, wrong length for y")
- }
-
- x := key.X.bigInt()
- y := key.Y.bigInt()
-
- if !curve.IsOnCurve(x, y) {
- return nil, errors.New("go-jose/go-jose: invalid EC key, X/Y are not on declared curve")
- }
-
- return &ecdsa.PublicKey{
- Curve: curve,
- X: x,
- Y: y,
- }, nil
-}
-func (key rawJSONWebKey) rsaPrivateKey() (*rsa.PrivateKey, error) {
- var missing []string
- switch {
- case key.N == nil:
- missing = append(missing, "N")
- case key.E == nil:
- missing = append(missing, "E")
- case key.D == nil:
- missing = append(missing, "D")
- case key.P == nil:
- missing = append(missing, "P")
- case key.Q == nil:
- missing = append(missing, "Q")
- }
-
- if len(missing) > 0 {
- return nil, fmt.Errorf("go-jose/go-jose: invalid RSA private key, missing %s value(s)", strings.Join(missing, ", "))
- }
-
- rv := &rsa.PrivateKey{
- PublicKey: rsa.PublicKey{
- N: key.N.bigInt(),
- E: key.E.toInt(),
- },
- D: key.D.bigInt(),
- Primes: []*big.Int{
- key.P.bigInt(),
- key.Q.bigInt(),
- },
- }
-
- if key.Dp != nil {
- rv.Precomputed.Dp = key.Dp.bigInt()
- }
- if key.Dq != nil {
- rv.Precomputed.Dq = key.Dq.bigInt()
- }
- if key.Qi != nil {
- rv.Precomputed.Qinv = key.Qi.bigInt()
- }
-
- err := rv.Validate()
- return rv, err
-}
-
-func (key rawJSONWebKey) rsaPublicKey() (*rsa.PublicKey, error) {
- if key.N == nil || key.E == nil {
- return nil, fmt.Errorf("go-jose/go-jose: invalid RSA key, missing n/e values")
- }
-
- return &rsa.PublicKey{
- N: key.N.bigInt(),
- E: key.E.toInt(),
- }, nil
-}
-func (b *byteBuffer) bytes() []byte {
- // Handling nil here allows us to transparently handle nil slices when serializing.
- if b == nil {
- return nil
- }
- return b.data
-}
-func (key rawJSONWebKey) symmetricKey() ([]byte, error) {
- if key.K == nil {
- return nil, fmt.Errorf("go-jose/go-jose: invalid OCT (symmetric) key, missing k value")
- }
- return key.K.bytes(), nil
-}
-func (key rawJSONWebKey) edPrivateKey() (ed25519.PrivateKey, error) {
- var missing []string
- switch {
- case key.D == nil:
- missing = append(missing, "D")
- case key.X == nil:
- missing = append(missing, "X")
- }
-
- if len(missing) > 0 {
- return nil, fmt.Errorf("go-jose/go-jose: invalid Ed25519 private key, missing %s value(s)", strings.Join(missing, ", "))
- }
-
- privateKey := make([]byte, ed25519.PrivateKeySize)
- copy(privateKey[0:32], key.D.bytes())
- copy(privateKey[32:], key.X.bytes())
- rv := ed25519.PrivateKey(privateKey)
- return rv, nil
-}
-func (key rawJSONWebKey) edPublicKey() (ed25519.PublicKey, error) {
- if key.X == nil {
- return nil, fmt.Errorf("go-jose/go-jose: invalid Ed key, missing x value")
- }
- publicKey := make([]byte, ed25519.PublicKeySize)
- copy(publicKey[0:32], key.X.bytes())
- rv := ed25519.PublicKey(publicKey)
- return rv, nil
-}
-
-func GenJswkey(parseBytes gjson.Result) (*jose.JSONWebKey, error) {
- var raw rawJSONWebKey
- var log wrapper.Log
- selClom(&raw, parseBytes)
-
- //
- certs, err := parseCertificateChain(raw.X5c)
- if err != nil {
- log.Errorf("err : %v", err)
- }
- var key interface{}
- var certPub interface{}
- var keyPub interface{}
-
- if len(certs) > 0 {
- // We need to check that leaf public key matches the key embedded in this
- // JWK, as required by the standard (see RFC 7517, Section 4.7). Otherwise
- // the JWK parsed could be semantically invalid. Technically, should also
- // check key usage fields and other extensions on the cert here, but the
- // standard doesn't exactly explain how they're supposed to map from the
- // JWK representation to the X.509 extensions.
- certPub = certs[0].PublicKey
- }
-
- switch raw.Kty {
- case "EC":
- if raw.D != nil {
- key, err = raw.ecPrivateKey()
- if err == nil {
- keyPub = key.(*ecdsa.PrivateKey).Public()
- }
- } else {
- key, err = raw.ecPublicKey()
- keyPub = key
- }
- case "RSA":
- if raw.D != nil {
- key, err = raw.rsaPrivateKey()
- if err == nil {
- keyPub = key.(*rsa.PrivateKey).Public()
- }
- } else {
- key, err = raw.rsaPublicKey()
- keyPub = key
- }
- case "oct":
- if certPub != nil {
- return nil, errors.New("go-jose/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain")
- }
- key, err = raw.symmetricKey()
- case "OKP":
- if raw.Crv == "Ed25519" && raw.X != nil {
- if raw.D != nil {
- key, err = raw.edPrivateKey()
- if err == nil {
- keyPub = key.(ed25519.PrivateKey).Public()
- }
- } else {
- key, err = raw.edPublicKey()
- keyPub = key
- }
- } else {
- err = fmt.Errorf("go-jose/go-jose: unknown curve %s'", raw.Crv)
- }
- default:
- err = fmt.Errorf("go-jose/go-jose: unknown json web key type '%s'", raw.Kty)
- }
-
- if err != nil {
- return nil, err
- }
-
- if certPub != nil && keyPub != nil {
-
- if !reflect.DeepEqual(certPub, keyPub) {
- return nil, errors.New("go-jose/go-jose: invalid JWK, public keys in key and x5c fields do not match")
- }
- }
-
- k := &jose.JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use, Certificates: certs}
-
- if raw.X5u != "" {
- k.CertificatesURL, err = url.Parse(raw.X5u)
- if err != nil {
- return nil, fmt.Errorf("go-jose/go-jose: invalid JWK, x5u header is invalid URL: %w", err)
- }
- }
-
- // x5t parameters are base64url-encoded SHA thumbprints
- // See RFC 7517, Section 4.8, https://tools.ietf.org/html/rfc7517#section-4.8
- x5tSHA1bytes, err := base64URLDecode(raw.X5tSHA1)
- if err != nil {
- return nil, errors.New("go-jose/go-jose: invalid JWK, x5t header has invalid encoding")
- }
-
- // RFC 7517, Section 4.8 is ambiguous as to whether the digest output should be byte or hex,
- // for this reason, after base64 decoding, if the size is sha1.Size it's likely that the value is a byte encoded
- // checksum so we skip this. Otherwise if the checksum was hex encoded we expect a 40 byte sized array so we'll
- // try to hex decode it. When Marshalling this value we'll always use a base64 encoded version of byte format checksum.
- if len(x5tSHA1bytes) == 2*sha1.Size {
- hx, err := hex.DecodeString(string(x5tSHA1bytes))
- if err != nil {
- return nil, fmt.Errorf("go-jose/go-jose: invalid JWK, unable to hex decode x5t: %v", err)
-
- }
- x5tSHA1bytes = hx
- }
-
- k.CertificateThumbprintSHA1 = x5tSHA1bytes
-
- x5tSHA256bytes, err := base64URLDecode(raw.X5tSHA256)
- if err != nil {
- return nil, errors.New("go-jose/go-jose: invalid JWK, x5t#S256 header has invalid encoding")
- }
-
- if len(x5tSHA256bytes) == 2*sha256.Size {
- hx256, err := hex.DecodeString(string(x5tSHA256bytes))
- if err != nil {
- return nil, fmt.Errorf("go-jose/go-jose: invalid JWK, unable to hex decode x5t#S256: %v", err)
- }
- x5tSHA256bytes = hx256
- }
-
- k.CertificateThumbprintSHA256 = x5tSHA256bytes
-
- x5tSHA1Len := len(k.CertificateThumbprintSHA1)
- x5tSHA256Len := len(k.CertificateThumbprintSHA256)
- if x5tSHA1Len > 0 && x5tSHA1Len != sha1.Size {
- return nil, errors.New("go-jose/go-jose: invalid JWK, x5t header is of incorrect size")
- }
- if x5tSHA256Len > 0 && x5tSHA256Len != sha256.Size {
- return nil, errors.New("go-jose/go-jose: invalid JWK, x5t#S256 header is of incorrect size")
- }
-
- // If certificate chain *and* thumbprints are set, verify correctness.
- if len(k.Certificates) > 0 {
- leaf := k.Certificates[0]
- sha1sum := sha1.Sum(leaf.Raw)
- sha256sum := sha256.Sum256(leaf.Raw)
-
- if len(k.CertificateThumbprintSHA1) > 0 && !bytes.Equal(sha1sum[:], k.CertificateThumbprintSHA1) {
- return nil, errors.New("go-jose/go-jose: invalid JWK, x5c thumbprint does not match x5t value")
- }
-
- if len(k.CertificateThumbprintSHA256) > 0 && !bytes.Equal(sha256sum[:], k.CertificateThumbprintSHA256) {
- return nil, errors.New("go-jose/go-jose: invalid JWK, x5c thumbprint does not match x5t#S256 value")
- }
- }
-
- return k, nil
-}
-
-func curveSize(crv elliptic.Curve) int {
- bits := crv.Params().BitSize
-
- div := bits / 8
- mod := bits % 8
-
- if mod == 0 {
- return div
- }
-
- return div + 1
-}
-func dSize(curve elliptic.Curve) int {
- order := curve.Params().P
- bitLen := order.BitLen()
- size := bitLen / 8
- if bitLen%8 != 0 {
- size++
- }
- return size
-}
-
-func (key rawJSONWebKey) ecPrivateKey() (*ecdsa.PrivateKey, error) {
- var curve elliptic.Curve
- switch key.Crv {
- case "P-256":
- curve = elliptic.P256()
- case "P-384":
- curve = elliptic.P384()
- case "P-521":
- curve = elliptic.P521()
- default:
- return nil, fmt.Errorf("go-jose/go-jose: unsupported elliptic curve '%s'", key.Crv)
- }
-
- if key.X == nil || key.Y == nil || key.D == nil {
- return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, missing x/y/d values")
- }
-
- // The length of this octet string MUST be the full size of a coordinate for
- // the curve specified in the "crv" parameter.
- // https://tools.ietf.org/html/rfc7518#section-6.2.1.2
- if curveSize(curve) != len(key.X.data) {
- return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for x")
- }
-
- if curveSize(curve) != len(key.Y.data) {
- return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for y")
- }
-
- // https://tools.ietf.org/html/rfc7518#section-6.2.2.1
- if dSize(curve) != len(key.D.data) {
- return nil, fmt.Errorf("go-jose/go-jose: invalid EC private key, wrong length for d")
- }
-
- x := key.X.bigInt()
- y := key.Y.bigInt()
-
- if !curve.IsOnCurve(x, y) {
- return nil, errors.New("go-jose/go-jose: invalid EC key, X/Y are not on declared curve")
- }
-
- return &ecdsa.PrivateKey{
- PublicKey: ecdsa.PublicKey{
- Curve: curve,
- X: x,
- Y: y,
- },
- D: key.D.bigInt(),
- }, nil
-}
-
-func selClom(raw *rawJSONWebKey, parseBytes gjson.Result) {
-
- raw.Use = parseBytes.Get("use").String()
-
- raw.Kty = parseBytes.Get("kty").String()
- raw.Kid = parseBytes.Get("kid").String()
- raw.Crv = parseBytes.Get("crv").String()
- raw.Alg = parseBytes.Get("alg").String()
-
- for _, item := range parseBytes.Get("x5c").Array() {
- scopes := item.String()
- raw.X5c = append(raw.X5c, scopes)
- }
-
- raw.X5u = parseBytes.Get("x5u").String()
- raw.X5tSHA1 = parseBytes.Get("x5t").String()
-
- raw.X5tSHA256 = parseBytes.Get("x5t#S256").String()
-
- //k
- if k := parseBytes.Get("k").Exists(); k {
- decode, err := base64URLDecode(parseBytes.Get("k").String())
- if err != nil {
- return
- }
- raw.K = newBuffer(decode)
- }
- //x
- if x := parseBytes.Get("x").Exists(); x {
- decode, err := base64URLDecode(parseBytes.Get("x").String())
- if err != nil {
- return
- }
- raw.X = newBuffer(decode)
- }
- //y
- if y := parseBytes.Get("y").Exists(); y {
- decode, err := base64URLDecode(parseBytes.Get("y").String())
- if err != nil {
- return
- }
- raw.Y = newBuffer(decode)
- }
- //n
- if n := parseBytes.Get("n").Exists(); n {
- decode, err := base64URLDecode(parseBytes.Get("n").String())
- if err != nil {
- return
- }
- raw.N = newBuffer(decode)
- }
- //e
- if e := parseBytes.Get("e").Exists(); e {
- decode, err := base64URLDecode(parseBytes.Get("e").String())
- if err != nil {
- return
- }
- raw.E = newBuffer(decode)
- }
- //d
- if d := parseBytes.Get("d").Exists(); d {
- decode, err := base64URLDecode(parseBytes.Get("d").String())
- if err != nil {
- return
- }
- raw.D = newBuffer(decode)
- }
- //p
- if p := parseBytes.Get("p").Exists(); p {
- decode, err := base64URLDecode(parseBytes.Get("p").String())
- if err != nil {
- return
- }
- raw.P = newBuffer(decode)
- }
- //q
- if q := parseBytes.Get("q").Exists(); q {
- decode, err := base64URLDecode(parseBytes.Get("q").String())
- if err != nil {
- return
- }
- raw.Q = newBuffer(decode)
- }
- //dp
- if dp := parseBytes.Get("dp").Exists(); dp {
- decode, err := base64URLDecode(parseBytes.Get("dp").String())
- if err != nil {
- return
- }
- raw.Dp = newBuffer(decode)
-
- }
- //dq
- if dq := parseBytes.Get("dq").Exists(); dq {
- decode, err := base64URLDecode(parseBytes.Get("dq").String())
- if err != nil {
- return
- }
- raw.Dq = newBuffer(decode)
- }
- //qi
- if qi := parseBytes.Get("qi").Exists(); qi {
- decode, err := base64URLDecode(parseBytes.Get("qi").String())
- if err != nil {
- return
- }
- raw.Qi = newBuffer(decode)
- }
-
-}
diff --git a/plugins/wasm-go/extensions/oidc/oc/provider.go b/plugins/wasm-go/extensions/oidc/oc/provider.go
deleted file mode 100644
index a807befb7..000000000
--- a/plugins/wasm-go/extensions/oidc/oc/provider.go
+++ /dev/null
@@ -1,290 +0,0 @@
-// Copyright (c) 2022 Alibaba Group Holding Ltd.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oc
-
-import (
- "errors"
- "fmt"
- "net/http"
- "net/url"
- "regexp"
- "strings"
-
- "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
- "github.com/go-jose/go-jose/v3"
- "github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
- "github.com/tidwall/gjson"
- "golang.org/x/oauth2"
-)
-
-var re = regexp.MustCompile("<[^>]*>")
-
-// OidcHandler 定义了处理 OpenID Connect(OIDC)认证流程的方法集合。
-// OIDC 是一个基于 OAuth 2.0 协议的身份验证和授权协议。
-type OidcHandler interface {
- // ProcessRedirect 负责处理来自 OIDC 身份提供者的重定向响应。
- // 该方法会从openid-configuration中获取 authorization_endpoint,
- // 并确保其中的状态以及任何可能的错误代码都得到正确处理。
- ProcessRedirect(log *wrapper.Log, cfg *Oatuh2Config) error
-
- // ProcessExchangeToken 负责执行令牌交换过程。
- // 该方法会从 openid-configuration 中获取 token_endpoint 和 jwks_uri,
- // 然后使用授权码来交换 access token 和 ID token。
- ProcessExchangeToken(log *wrapper.Log, cfg *Oatuh2Config) error
-
- // ProcessVerify 负责验证 ID 令牌的有效性。
- // 通过使用 openid-configuration 中的获取的 jwks_uri 配置信息来验证 ID 令牌的签名和有效性。
- ProcessVerify(log *wrapper.Log, cfg *Oatuh2Config) error
-}
-type DefaultOAuthHandler struct {
-}
-
-func NewDefaultOAuthHandler() OidcHandler {
- return &DefaultOAuthHandler{}
-}
-
-func ProcessHTTPCall(log *wrapper.Log, cfg *Oatuh2Config, callback func(responseBody []byte)) error {
- wellKnownPath := strings.TrimSuffix(cfg.Path, "/") + "/.well-known/openid-configuration"
- if err := cfg.Client.Get(wellKnownPath, nil, func(statusCode int, responseHeaders http.Header, responseBody []byte) {
- if err := ValidateHTTPResponse(statusCode, responseHeaders, responseBody); err != nil {
- cleanedBody := re.ReplaceAllString(string(responseBody), "")
- SendError(log, fmt.Sprintf("ValidateHTTPResponse failed , status : %v err : %v err_info: %v ", statusCode, err, cleanedBody), statusCode, "oidc.bad_well_known_response")
- return
- }
- callback(responseBody)
-
- }, uint32(cfg.Timeout)); err != nil {
- return err
- }
-
- return nil
-}
-
-func (d *DefaultOAuthHandler) ProcessRedirect(log *wrapper.Log, cfg *Oatuh2Config) error {
- return ProcessHTTPCall(log, cfg, func(responseBody []byte) {
- state, _ := Nonce(32)
- statStr := GenState(state, cfg.ClientSecret, cfg.RedirectURL)
- cfg.Endpoint.AuthURL = gjson.ParseBytes(responseBody).Get("authorization_endpoint").String()
- if cfg.Endpoint.AuthURL == "" {
- SendError(log, "Missing 'authorization_endpoint' in the OpenID configuration response.", http.StatusInternalServerError, "oidc.auth_endpoint_missing")
- return
- }
-
- var opts oauth2.AuthCodeOption
- if !cfg.SkipNonceCheck {
- opts = SetNonce(string(cfg.CookieData.Nonce))
- }
- codeURL := cfg.AuthCodeURL(statStr, opts)
- proxywasm.SendHttpResponseWithDetail(http.StatusFound, "oidc.authed", [][2]string{
- {"Location", codeURL},
- }, nil, -1)
- return
- })
-}
-
-func (d *DefaultOAuthHandler) ProcessExchangeToken(log *wrapper.Log, cfg *Oatuh2Config) error {
- return ProcessHTTPCall(log, cfg, func(responseBody []byte) {
-
- PvRJson := gjson.ParseBytes(responseBody)
- cfg.Endpoint.TokenURL = PvRJson.Get("token_endpoint").String()
- if cfg.Endpoint.TokenURL == "" {
- SendError(log, "Missing 'token_endpoint' in the OpenID configuration response.", http.StatusInternalServerError, "oidc.token_endpoint_missing")
- return
- }
- cfg.JwksURL = PvRJson.Get("jwks_uri").String()
- if cfg.JwksURL == "" {
- SendError(log, "Missing 'jwks_uri' in the OpenID configuration response.", http.StatusInternalServerError, "oidc.jwks_uri_missing")
- return
- }
- cfg.Option.AuthStyle = AuthStyle(cfg.Endpoint.AuthStyle)
-
- if err := processToken(log, cfg); err != nil {
- SendError(log, fmt.Sprintf("ProcessToken failed : err %v", err), http.StatusInternalServerError, "oidc.process_token_failed")
- return
- }
- })
-}
-
-func (d *DefaultOAuthHandler) ProcessVerify(log *wrapper.Log, cfg *Oatuh2Config) error {
- return ProcessHTTPCall(log, cfg, func(responseBody []byte) {
- PvRJson := gjson.ParseBytes(responseBody)
-
- cfg.JwksURL = PvRJson.Get("jwks_uri").String()
- if cfg.JwksURL == "" {
- SendError(log, "Missing 'token_endpoint' in the OpenID configuration response.", http.StatusInternalServerError, "oidc.token_endpoint_missing")
- return
- }
- var algs []string
- for _, a := range PvRJson.Get("id_token_signing_alg_values_supported").Array() {
- if SupportedAlgorithms[a.String()] {
- algs = append(algs, a.String())
- }
- }
- cfg.SupportedSigningAlgs = algs
- if err := processTokenVerify(log, cfg); err != nil {
- SendError(log, fmt.Sprintf("failed to verify token: %v", err), http.StatusInternalServerError, "oidc.verify_token_failed")
- return
- }
- })
-}
-
-func processToken(log *wrapper.Log, cfg *Oatuh2Config) error {
- parsedURL, err := url.Parse(cfg.Endpoint.TokenURL)
- if err != nil {
- return fmt.Errorf("invalid TokenURL: %v", err)
- }
-
- var token Token
- urlVales := ReturnURL(cfg.RedirectURL, cfg.Option.Code)
- needsAuthStyleProbe := cfg.Option.AuthStyle == AuthStyleUnknown
- if needsAuthStyleProbe {
- if style, ok := LookupAuthStyle(cfg.Endpoint.TokenURL); ok {
- cfg.Option.AuthStyle = style
- } else {
- cfg.Option.AuthStyle = AuthStyleInHeader
- }
- }
-
- headers, body, err := NewTokenRequest(cfg.Endpoint.TokenURL, cfg.ClientID, cfg.ClientSecret, urlVales, cfg.Option.AuthStyle)
- cb := func(statusCode int, responseHeaders http.Header, responseBody []byte) {
- if err := ValidateHTTPResponse(statusCode, responseHeaders, responseBody); err != nil {
- cleanedBody := re.ReplaceAllString(string(responseBody), "")
- SendError(log, fmt.Sprintf("Valid failed , status : %v err : %v err_info: %v ", statusCode, err, cleanedBody), statusCode, "oidc.bad_token_response")
- return
- }
-
- tk, err := UnmarshalToken(&token, responseHeaders, responseBody)
- if err != nil {
- SendError(log, fmt.Sprintf("UnmarshalToken error: %v", err), http.StatusInternalServerError, "oidc.extract_token_failed")
- return
- }
-
- if tk != nil && token.RefreshToken == "" {
- token.RefreshToken = urlVales.Get("refresh_token")
- }
-
- betoken := TokenFromInternal(tk)
-
- rawIDToken, ok := betoken.Extra("id_token").(string)
- if !ok {
- SendError(log, fmt.Sprintf("No id_token field in oauth2 token."), http.StatusInternalServerError, "oidc.id_token_missing")
- return
- }
- cfg.Option.RawIdToken = rawIDToken
-
- err = processTokenVerify(log, cfg)
- if err != nil {
- SendError(log, fmt.Sprintf("failed to verify token: %v", err), http.StatusInternalServerError, "oidc.verify_token_failed")
- return
- }
-
- }
-
- err = cfg.Client.Post(parsedURL.Path, headers, body, cb, uint32(cfg.Timeout))
- if err != nil {
- return fmt.Errorf("HTTP POST error: %v", err)
- }
-
- return nil
-}
-
-func processTokenVerify(log *wrapper.Log, cfg *Oatuh2Config) error {
- keySet := jose.JSONWebKeySet{}
- idTokenVerify := cfg.Verifier(&IDConfig{
- ClientID: cfg.ClientID,
- SupportedSigningAlgs: cfg.SupportedSigningAlgs,
- SkipExpiryCheck: cfg.SkipExpiryCheck,
- SkipNonceCheck: cfg.SkipNonceCheck,
- })
-
- defaultHandlerForRedirect := NewDefaultOAuthHandler()
- parsedURL, err := url.Parse(cfg.JwksURL)
- if err != nil {
- log.Errorf("JwksURL is invalid err : %v", err)
- return err
- }
- cb := func(statusCode int, responseHeaders http.Header, responseBody []byte) {
- if err := ValidateHTTPResponse(statusCode, responseHeaders, responseBody); err != nil {
- cleanedBody := re.ReplaceAllString(string(responseBody), "")
- SendError(log, fmt.Sprintf("Valid failed , status : %v err : %v err_info: %v ", statusCode, err, cleanedBody), statusCode, "oidc.bad_validate_response")
- return
- }
-
- res := gjson.ParseBytes(responseBody)
- for _, val := range res.Get("keys").Array() {
- jsw, err := GenJswkey(val)
- if err != nil {
- log.Errorf("err: %v", err)
- SendError(log, fmt.Sprintf("GenJswkey error:%v", err), http.StatusInternalServerError, "oidc.gen_jsw_key_failed")
- return
- }
- keySet.Keys = append(keySet.Keys, *jsw)
- }
- idtoken, err := idTokenVerify.VerifyToken(cfg.Option.RawIdToken, keySet)
-
- if err != nil {
- log.Errorf("VerifyToken err : %v ", err)
- defaultHandlerForRedirect.ProcessRedirect(log, cfg)
- return
- }
- if !cfg.SkipNonceCheck && Access == cfg.Option.Mod {
- err := verifyNonce(idtoken, cfg)
- if err != nil {
- log.Error("VerifyNonce failed")
- defaultHandlerForRedirect.ProcessRedirect(log, cfg)
- return
- }
- }
-
- //回发和放行
- if cfg.Option.Mod == Access {
- proxywasm.AddHttpRequestHeader("Authorization", "Bearer "+cfg.Option.RawIdToken)
- proxywasm.ResumeHttpRequest()
- return
- }
-
- cfg.CookieOption.Expire = idtoken.Expiry
- cfg.CookieData.IDToken = cfg.Option.RawIdToken
- cfg.CookieData.ExpiresOn = idtoken.Expiry
- cfg.CookieData.Secret = cfg.CookieOption.Secret
-
- cookieHeader, err := SerializeAndEncryptCookieData(cfg.CookieData, cfg.CookieOption.Secret, cfg.CookieOption)
- if err != nil {
- SendError(log, fmt.Sprintf("SerializeAndEncryptCookieData failed : %v", err), http.StatusInternalServerError, "oidc.gen_cookie_failed")
- return
- }
- proxywasm.SendHttpResponseWithDetail(http.StatusFound, "oidc.token_verified", [][2]string{
- {"Location", cfg.ClientUrl},
- {"Set-Cookie", cookieHeader},
- }, nil, -1)
-
- return
- }
-
- if err := cfg.Client.Get(parsedURL.Path, nil, cb, uint32(cfg.Timeout)); err != nil {
- log.Errorf("client.Get error: %v", err)
- return err
- }
- return nil
-}
-
-func verifyNonce(idtoken *IDToken, cfg *Oatuh2Config) error {
- parts := strings.Split(idtoken.Nonce, ".")
- if len(parts) != 2 {
- return errors.New("nonce format err expect 2 parts")
- }
- stateval, signature := parts[0], parts[1]
- return VerifyState(stateval, signature, cfg.ClientSecret, cfg.RedirectURL)
-}
diff --git a/plugins/wasm-go/extensions/oidc/oc/util.go b/plugins/wasm-go/extensions/oidc/oc/util.go
deleted file mode 100644
index 666f3774b..000000000
--- a/plugins/wasm-go/extensions/oidc/oc/util.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (c) 2022 Alibaba Group Holding Ltd.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package oc
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "net/url"
- "strings"
- "time"
-
- "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
- "github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
- "github.com/tidwall/gjson"
-)
-
-func ValidateHTTPResponse(statusCode int, headers http.Header, body []byte) error {
- contentType := headers.Get("Content-Type")
- if statusCode != http.StatusOK {
- return errors.New("call failed with status code")
- }
- if !strings.Contains(contentType, "application/json") {
- return fmt.Errorf("expected Content-Type = application/json , but got %s", contentType)
- }
- if !gjson.ValidBytes(body) {
- return errors.New("invalid JSON format in response body")
- }
- return nil
-}
-
-// GetParams 返回顺序 cookie code state
-func GetParams(name, cookie, path, key string) (oidcCookieValue, code, state string, err error) {
- u, err := url.Parse(path)
- if err != nil {
- return "", "", "", err
- }
- query := u.Query()
- code, state = query.Get("code"), query.Get("state")
-
- cookiePairs := strings.Split(cookie, "; ")
- for _, pair := range cookiePairs {
- keyValue := strings.Split(pair, "=")
- if keyValue[0] == name {
- oidcCookieValue = keyValue[1]
- break
- }
- }
-
- oidcCookieValue, err = url.QueryUnescape(oidcCookieValue)
- if err != nil {
- return "", "", "", err
- }
- oidcCookieValue, err = Decrypt(oidcCookieValue, key)
- return oidcCookieValue, code, state, nil
-}
-
-func SendError(log *wrapper.Log, errMsg string, status int, statusDetail string) {
- log.Errorf(errMsg)
- proxywasm.SendHttpResponseWithDetail(uint32(status), statusDetail, nil, []byte(errMsg), -1)
-}
-
-type jsonTime time.Time
-
-func (j *jsonTime) UnmarshalJSON(b []byte) error {
- var n json.Number
- if err := json.Unmarshal(b, &n); err != nil {
- return err
- }
- var unix int64
-
- if t, err := n.Int64(); err == nil {
- unix = t
- } else {
- f, err := n.Float64()
- if err != nil {
- return err
- }
- unix = int64(f)
- }
- *j = jsonTime(time.Unix(unix, 0))
- return nil
-}
-
-type audience []string
-
-func (a *audience) UnmarshalJSON(b []byte) error {
- var s string
- if json.Unmarshal(b, &s) == nil {
- *a = audience{s}
- return nil
- }
- var auds []string
- if err := json.Unmarshal(b, &auds); err != nil {
- return err
- }
- *a = auds
- return nil
-}
diff --git a/plugins/wasm-go/extensions/oidc/oc/verifer.go b/plugins/wasm-go/extensions/oidc/oc/verifer.go
deleted file mode 100644
index 033712dec..000000000
--- a/plugins/wasm-go/extensions/oidc/oc/verifer.go
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- Copyright 2023 go-oidc
-
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-
-* http://www.apache.org/licenses/LICENSE-2.0
-
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-package oc
-
-import (
- "bytes"
- "encoding/base64"
- "encoding/json"
- "errors"
- "fmt"
- "strings"
- "time"
-
- "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
- "github.com/go-jose/go-jose/v3"
-)
-
-// IDTokenVerifierConfig
-type IDTokenVerifier struct {
- config *IDConfig
- issuer string
-}
-
-const (
- issuerGoogleAccounts = "https://accounts.google.com"
- issuerGoogleAccountsNoScheme = "accounts.google.com"
-
- LEEWAY = 5 * time.Minute
-)
-
-type IDToken struct {
- Issuer string
- Audience []string
- Subject string
- Expiry time.Time
- IssuedAt time.Time
- Nonce string
- AccessTokenHash string
- sigAlgorithm string
- claims []byte
- distributedClaims map[string]claimSource
-}
-type TokenExpiredError struct {
- Expiry time.Time
-}
-
-func (e *TokenExpiredError) Error() string {
- return fmt.Sprintf("oidc: token is expired (Token Expiry: %v)", e.Expiry)
-}
-func (i *IDToken) Claims(v interface{}) error {
- if i.claims == nil {
- return errors.New("oidc: claims not set")
- }
- return json.Unmarshal(i.claims, v)
-}
-
-func (v *IDTokenVerifier) VerifyToken(rawIDToken string, keySet jose.JSONWebKeySet) (*IDToken, error) {
- var log wrapper.Log
- payload, err := parseJWT(rawIDToken)
- if err != nil {
- return nil, fmt.Errorf(" malformed jwt: %v", err)
- }
- var token idToken
- if err := json.Unmarshal(payload, &token); err != nil {
- log.Errorf("idToken Unmarshal error : %v ", err)
- return nil, fmt.Errorf("failed to unmarshal claims: %v", err)
- }
-
- distributedClaims := make(map[string]claimSource)
-
- //step through the token to map claim names to claim sources
- for cn, src := range token.ClaimNames {
- if src == "" {
- return nil, fmt.Errorf("failed to obtain source from claim name")
- }
- s, ok := token.ClaimSources[src]
- if !ok {
- return nil, fmt.Errorf("source does not exist")
- }
- distributedClaims[cn] = s
- }
-
- t := &IDToken{
- Issuer: token.Issuer,
- Subject: token.Subject,
- Audience: []string(token.Audience),
- Expiry: time.Time(token.Expiry),
- IssuedAt: time.Time(token.IssuedAt),
- Nonce: token.Nonce,
- AccessTokenHash: token.AtHash,
- claims: payload,
- distributedClaims: distributedClaims,
- }
-
- // Check issuer.
- if !v.config.SkipIssuerCheck && t.Issuer != v.issuer {
- // Google sometimes returns "accounts.google.com" as the issuer claim instead of
- // the required "https://accounts.google.com". Detect this case and allow it only
- // for Google.
- //
- // We will not add hooks to let other providers go off spec like this.
- if !(v.issuer == issuerGoogleAccounts && t.Issuer == issuerGoogleAccountsNoScheme) {
- return nil, fmt.Errorf("oidc: id token issued by a different provider, expected %q got %q", v.issuer, t.Issuer)
- }
- }
-
- if v.config.ClientID != "" {
- if !contains(t.Audience, v.config.ClientID) {
- return nil, fmt.Errorf("oidc: expected audience %q got %q", v.config.ClientID, t.Audience)
- }
- }
-
- // If a SkipExpiryCheck is false, make sure token is not expired.
- if !v.config.SkipExpiryCheck {
- now := time.Now
- if v.config.Now != nil {
- now = v.config.Now
- }
- nowTime := now()
-
- if t.Expiry.Before(nowTime) {
- return nil, &TokenExpiredError{Expiry: t.Expiry}
- }
-
- // If nbf claim is provided in token, ensure that it is indeed in the past.
- if token.NotBefore != nil {
- nbfTime := time.Time(*token.NotBefore)
- // Set to 5 minutes since this is what other OpenID Connect providers do to deal with clock skew.
- // https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/6.12.2/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs#L149-L153
-
- if nowTime.Add(LEEWAY).Before(nbfTime) {
- return nil, fmt.Errorf("oidc: current time %v before the nbf (not before) time: %v", nowTime, nbfTime)
- }
- }
- }
-
- jws, err := jose.ParseSigned(rawIDToken)
- if err != nil {
- return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
- }
-
- switch len(jws.Signatures) {
- case 0:
- return nil, fmt.Errorf("oidc: id token not signed")
- case 1:
- default:
- return nil, fmt.Errorf("oidc: multiple signatures on id token not supported")
- }
-
- sig := jws.Signatures[0]
- supportedSigAlgs := v.config.SupportedSigningAlgs
-
- if len(supportedSigAlgs) == 0 {
- supportedSigAlgs = []string{RS256}
- }
-
- if !contains(supportedSigAlgs, sig.Header.Algorithm) {
- return nil, fmt.Errorf("oidc: id token signed with unsupported algorithm, expected %q got %q", supportedSigAlgs, sig.Header.Algorithm)
- }
-
- t.sigAlgorithm = sig.Header.Algorithm
-
- keyID := ""
- for _, sig := range jws.Signatures {
- keyID = sig.Header.KeyID
- break
- }
-
- for _, key := range keySet.Keys {
- if keyID == "" || key.KeyID == keyID {
- if gotPayload, err := jws.Verify(&key); err == nil {
- if !bytes.Equal(gotPayload, payload) {
- return nil, errors.New("oidc: internal error, payload parsed did not match previous payload")
- }
- }
- }
- }
-
- return t, nil
-}
-func contains(sli []string, ele string) bool {
- for _, s := range sli {
- if s == ele {
- return true
- }
- }
- return false
-}
-func parseJWT(p string) ([]byte, error) {
- parts := strings.Split(p, ".")
- if len(parts) < 2 {
- return nil, fmt.Errorf("oidc: malformed jwt, expected 3 parts got %d", len(parts))
- }
- payload, err := base64.RawURLEncoding.DecodeString(parts[1])
- if err != nil {
- return nil, fmt.Errorf("oidc: malformed jwt payload: %v", err)
- }
- return payload, nil
-}
-func (cfg *Oatuh2Config) Verifier(config *IDConfig) *IDTokenVerifier {
- return &IDTokenVerifier{
- config: config,
- issuer: cfg.Issuer,
- }
-}