mirror of
https://github.com/alibaba/higress.git
synced 2026-06-09 12:47:28 +08:00
feat(plugin): implement golang version of plugin jwt-auth (#743)
Signed-off-by: Ink33 <Ink33@smlk.org>
This commit is contained in:
34
plugins/wasm-go/extensions/jwt-auth/config/checker.go
Normal file
34
plugins/wasm-go/extensions/jwt-auth/config/checker.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2023 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 config
|
||||
|
||||
type GlobalAuthType int
|
||||
|
||||
const (
|
||||
GlobalAuthTrue GlobalAuthType = 10000 + iota
|
||||
GlobalAuthFalse
|
||||
GlobalAuthNoSet
|
||||
)
|
||||
|
||||
func (c *JWTAuthConfig) GlobalAuthCheck() GlobalAuthType {
|
||||
if c.GlobalAuth == nil {
|
||||
return GlobalAuthNoSet
|
||||
}
|
||||
|
||||
if *c.GlobalAuth {
|
||||
return GlobalAuthTrue
|
||||
}
|
||||
return GlobalAuthFalse
|
||||
}
|
||||
125
plugins/wasm-go/extensions/jwt-auth/config/config.go
Normal file
125
plugins/wasm-go/extensions/jwt-auth/config/config.go
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) 2023 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 config
|
||||
|
||||
var (
|
||||
// DefaultClaimToHeaderOverride 是 claim_to_override 中 override 字段的默认值
|
||||
DefaultClaimToHeaderOverride = true
|
||||
|
||||
// DefaultClockSkewSeconds 是 ClockSkewSeconds 的默认值
|
||||
DefaultClockSkewSeconds = int64(60)
|
||||
|
||||
// DefaultKeepToken 是 KeepToken 的默认值
|
||||
DefaultKeepToken = true
|
||||
|
||||
// DefaultFromHeader 是 from_header 的默认值
|
||||
DefaultFromHeader = []FromHeader{{
|
||||
Name: "Authorization",
|
||||
ValuePrefix: "Bearer ",
|
||||
}}
|
||||
|
||||
// DefaultFromParams 是 from_params 的默认值
|
||||
DefaultFromParams = []string{"access_token"}
|
||||
|
||||
// DefaultFromCookies 是 from_cookies 的默认值
|
||||
DefaultFromCookies = []string{}
|
||||
)
|
||||
|
||||
// JWTAuthConfig defines the struct of the global config of higress wasm plugin jwt-auth.
|
||||
// https://higress.io/zh-cn/docs/plugins/jwt-auth
|
||||
type JWTAuthConfig struct {
|
||||
// 全局配置
|
||||
//
|
||||
// Consumers 配置服务的调用者,用于对请求进行认证
|
||||
Consumers []*Consumer `json:"consumers"`
|
||||
|
||||
// 全局配置
|
||||
//
|
||||
// GlobalAuth 若配置为true,则全局生效认证机制;
|
||||
// 若配置为false,则只对做了配置的域名和路由生效认证机制;
|
||||
// 若不配置则仅当没有域名和路由配置时全局生效(兼容机制)
|
||||
GlobalAuth *bool `json:"global_auth,omitempty"`
|
||||
|
||||
// 域名和路由级配置
|
||||
//
|
||||
// Allow 对于符合匹配条件的请求,配置允许访问的consumer名称
|
||||
Allow []string `json:"allow"`
|
||||
}
|
||||
|
||||
// Consumer 配置服务的调用者,用于对请求进行认证
|
||||
type Consumer struct {
|
||||
// Name 配置该consumer的名称
|
||||
Name string `json:"name"`
|
||||
|
||||
// JWKs 指定的json格式字符串,是由验证JWT中签名的公钥(或对称密钥)组成的Json Web Key Set
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/rfc7517
|
||||
JWKs string `json:"jwks"`
|
||||
|
||||
// Issuer JWT的签发者,需要和payload中的iss字段保持一致
|
||||
Issuer string `json:"issuer"`
|
||||
|
||||
// ClaimsToHeaders 抽取JWT的payload中指定字段,设置到指定的请求头中转发给后端
|
||||
ClaimsToHeaders *[]ClaimsToHeader `json:"claims_to_headers,omitempty"`
|
||||
|
||||
// FromHeaders 从指定的请求头中抽取JWT
|
||||
//
|
||||
// 默认值为 [{"name":"Authorization","value_prefix":"Bearer "}]
|
||||
//
|
||||
// 只有当from_headers,from_params,from_cookies均未配置时,才会使用默认值
|
||||
FromHeaders *[]FromHeader `json:"from_headers,omitempty"`
|
||||
|
||||
// FromParams 从指定的URL参数中抽取JWT
|
||||
//
|
||||
// 默认值为 access_token
|
||||
//
|
||||
// 只有当from_headers,from_params,from_cookies均未配置时,才会使用默认值
|
||||
FromParams *[]string `json:"from_params,omitempty"`
|
||||
|
||||
// FromCookies 从指定的cookie中抽取JWT
|
||||
FromCookies *[]string `json:"from_cookies,omitempty"`
|
||||
|
||||
// ClockSkewSeconds 校验JWT的exp和iat字段时允许的时钟偏移量,单位为秒
|
||||
//
|
||||
// 默认值为 60
|
||||
ClockSkewSeconds *int64 `json:"clock_skew_seconds,omitempty"`
|
||||
|
||||
// KeepToken 转发给后端时是否保留JWT
|
||||
//
|
||||
// 默认值为 true
|
||||
KeepToken *bool `json:"keep_token,omitempty"`
|
||||
}
|
||||
|
||||
// ClaimsToHeader 抽取JWT的payload中指定字段,设置到指定的请求头中转发给后端
|
||||
type ClaimsToHeader struct {
|
||||
// Claim JWT payload中的指定字段,要求必须是字符串或无符号整数类型
|
||||
Claim string `json:"claim"`
|
||||
|
||||
// Header 从payload取出字段的值设置到这个请求头中,转发给后端
|
||||
Header string `json:"header"`
|
||||
|
||||
// Override true时,存在同名请求头会进行覆盖;false时,追加同名请求头
|
||||
//
|
||||
// 默认值为 true
|
||||
Override *bool `json:"override,omitempty"`
|
||||
}
|
||||
|
||||
// FromHeader 从指定的请求头中抽取JWT
|
||||
type FromHeader struct {
|
||||
// Name 抽取JWT的请求header
|
||||
Name string `json:"name"`
|
||||
// ValuePrefix 对请求header的value去除此前缀,剩余部分作为JWT
|
||||
ValuePrefix string `json:"value_prefix"`
|
||||
}
|
||||
138
plugins/wasm-go/extensions/jwt-auth/config/parser.go
Normal file
138
plugins/wasm-go/extensions/jwt-auth/config/parser.go
Normal file
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) 2023 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 config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/go-jose/go-jose/v3"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// RuleSet 插件是否至少在一个 domain 或 route 上生效
|
||||
var RuleSet bool
|
||||
|
||||
// ParseGlobalConfig 从wrapper提供的配置中解析并转换到插件运行时需要使用的配置。
|
||||
// 此处解析的是全局配置,域名和路由级配置由 ParseRuleConfig 负责。
|
||||
func ParseGlobalConfig(json gjson.Result, config *JWTAuthConfig, log wrapper.Log) error {
|
||||
RuleSet = false
|
||||
consumers := json.Get("consumers")
|
||||
if !consumers.IsArray() {
|
||||
return fmt.Errorf("failed to parse configuration for consumers: consumers is not a array")
|
||||
}
|
||||
|
||||
consumerNames := map[string]struct{}{}
|
||||
for _, v := range consumers.Array() {
|
||||
c, err := ParseConsumer(v, consumerNames)
|
||||
if err != nil {
|
||||
log.Warn(err.Error())
|
||||
continue
|
||||
}
|
||||
config.Consumers = append(config.Consumers, c)
|
||||
}
|
||||
if len(config.Consumers) == 0 {
|
||||
return fmt.Errorf("at least one consumer should be configured for a rule")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseRuleConfig 从wrapper提供的配置中解析并转换到插件运行时需要使用的配置。
|
||||
// 此处解析的是域名和路由级配置,全局配置由 ParseConfig 负责。
|
||||
func ParseRuleConfig(json gjson.Result, global JWTAuthConfig, config *JWTAuthConfig, log wrapper.Log) error {
|
||||
// override config via global
|
||||
*config = global
|
||||
|
||||
allow := json.Get("allow")
|
||||
if !allow.Exists() {
|
||||
return fmt.Errorf("allow is required")
|
||||
}
|
||||
|
||||
if len(allow.Array()) == 0 {
|
||||
return fmt.Errorf("allow cannot be empty")
|
||||
}
|
||||
|
||||
for _, item := range allow.Array() {
|
||||
config.Allow = append(config.Allow, item.String())
|
||||
}
|
||||
|
||||
RuleSet = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseConsumer(consumer gjson.Result, names map[string]struct{}) (c *Consumer, err error) {
|
||||
c = &Consumer{}
|
||||
|
||||
// 从gjson中取得原始JSON字符串,并使用标准库反序列化,以降低代码复杂度。
|
||||
err = json.Unmarshal([]byte(consumer.Raw), c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse consumer: %s", err.Error())
|
||||
}
|
||||
|
||||
// 检查consumer是否重复
|
||||
if _, ok := names[c.Name]; ok {
|
||||
return nil, fmt.Errorf("consumer already exists: %s", c.Name)
|
||||
}
|
||||
|
||||
// 检查JWKs是否合法
|
||||
jwks := &jose.JSONWebKeySet{}
|
||||
err = json.Unmarshal([]byte(c.JWKs), jwks)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("jwks is invalid, consumer:%s, status:%s, jwks:%s", c.Name, err.Error(), c.JWKs)
|
||||
}
|
||||
|
||||
// 检查是否需要使用默认jwt抽取来源
|
||||
if c.FromHeaders == nil && c.FromParams == nil && c.FromCookies == nil {
|
||||
c.FromHeaders = &DefaultFromHeader
|
||||
c.FromParams = &DefaultFromParams
|
||||
c.FromCookies = &DefaultFromCookies
|
||||
}
|
||||
|
||||
// 检查ClaimsToHeaders
|
||||
if c.ClaimsToHeaders != nil {
|
||||
// header去重
|
||||
c2h := map[string]struct{}{}
|
||||
|
||||
// 此处需要先把指针解引用到临时变量
|
||||
tmp := *c.ClaimsToHeaders
|
||||
for i := range tmp {
|
||||
if _, ok := c2h[tmp[i].Header]; ok {
|
||||
return nil, fmt.Errorf("claim to header already exists: %s", c2h[tmp[i].Header])
|
||||
}
|
||||
c2h[tmp[i].Header] = struct{}{}
|
||||
|
||||
// 为Override填充默认值
|
||||
if tmp[i].Override == nil {
|
||||
tmp[i].Override = &DefaultClaimToHeaderOverride
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 为ClockSkewSeconds填充默认值
|
||||
if c.ClockSkewSeconds == nil {
|
||||
c.ClockSkewSeconds = &DefaultClockSkewSeconds
|
||||
}
|
||||
|
||||
// 为KeepToken填充默认值
|
||||
if c.KeepToken == nil {
|
||||
c.KeepToken = &DefaultKeepToken
|
||||
}
|
||||
|
||||
// consumer合法,记录consumer名称
|
||||
names[c.Name] = struct{}{}
|
||||
return c, nil
|
||||
}
|
||||
Reference in New Issue
Block a user