Files
higress/plugins/wasm-go/extensions/jwt-auth/handler/verify.go

214 lines
5.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 handler
import (
"encoding/json"
"fmt"
"time"
cfg "github.com/alibaba/higress/plugins/wasm-go/extensions/jwt-auth/config"
"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/jwt"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
)
var protectionSpace = "MSE Gateway" // 认证失败时,返回响应头 WWW-Authenticate: JWT realm=MSE Gateway
type ErrDenied struct {
msg string
denied func() types.Action
}
type Logger interface {
Warnf(format string, args ...interface{})
}
type HeaderProvider interface {
GetHttpRequestHeader(key string) (string, error)
ReplaceHttpRequestHeader(key string, value string) error
RemoveHttpRequestHeader(key string) error
}
type proxywasmProvider struct{}
func (p *proxywasmProvider) GetHttpRequestHeader(key string) (string, error) {
return proxywasm.GetHttpRequestHeader(key)
}
func (p *proxywasmProvider) ReplaceHttpRequestHeader(key string, value string) error {
return proxywasm.ReplaceHttpRequestHeader(key, value)
}
func (p *proxywasmProvider) RemoveHttpRequestHeader(key string) error {
return proxywasm.RemoveHttpRequestHeader(key)
}
func (e *ErrDenied) Error() string {
return e.msg
}
func consumerVerify(consumer *cfg.Consumer, verifyTime time.Time, header HeaderProvider, log Logger) error {
tokenStr := extractToken(*consumer.KeepToken, consumer, header, log)
if tokenStr == "" {
return &ErrDenied{
msg: fmt.Sprintf("jwt is missing, consumer: %s", consumer.Name),
denied: deniedJWTMissing,
}
}
// 当前版本的higress暂不支持jwe此处用ParseSigned
token, err := jwt.ParseSigned(tokenStr)
if err != nil {
return &ErrDenied{
msg: fmt.Sprintf("jwt parse failed, consumer: %s, token: %s, reason: %s",
consumer.Name,
tokenStr,
err.Error(),
),
denied: deniedJWTVerificationFails,
}
}
// 此处可以直接使用 JSON 反序列 jwks
jwks := jose.JSONWebKeySet{}
err = json.Unmarshal([]byte(consumer.JWKs), &jwks)
if err != nil {
return &ErrDenied{
msg: fmt.Sprintf("jwt parse failed, consumer: %s, token: %s, reason: %s",
consumer.Name,
tokenStr,
err.Error(),
),
denied: deniedJWTVerificationFails,
}
}
out := jwt.Claims{}
rawClaims := map[string]any{}
// 提前确认 kid 状态
var kid string
var key jose.JSONWebKey
for _, header := range token.Headers {
if header.KeyID != "" {
kid = header.KeyID
break
}
}
// 没有 kid 时选择第一个 key
if kid == "" {
key = jwks.Keys[0]
}
keys := jwks.Key(kid)
if len(keys) == 0 { // kid 不存在时选择第一个 key
key = jwks.Keys[0]
} else {
key = keys[0]
}
// Claims 支持直接传入 jose 的 jwk
// 无需额外调用verifyclaims内部已进行验证
err = token.Claims(key, &out)
if err != nil {
return &ErrDenied{
msg: fmt.Sprintf("jwt verify failed, consumer: %s, token: %s, reason: %s",
consumer.Name,
tokenStr,
err.Error(),
),
denied: deniedJWTVerificationFails,
}
}
token.UnsafeClaimsWithoutVerification(&rawClaims)
if out.Issuer != consumer.Issuer {
return &ErrDenied{
msg: fmt.Sprintf("jwt verify failed, consumer: %s, token: %s, reason: issuer does not equal",
consumer.Name,
tokenStr,
),
denied: deniedJWTVerificationFails,
}
}
// 检查是否过期
err = out.ValidateWithLeeway(
jwt.Expected{
Issuer: consumer.Issuer,
Time: verifyTime,
},
time.Duration(*consumer.ClockSkewSeconds)*time.Second,
)
if err != nil {
return &ErrDenied{
msg: fmt.Sprintf("jwt verify failed, consumer: %s, token: %s, reason: %s",
consumer.Name,
tokenStr,
err.Error(),
),
denied: deniedJWTExpired,
}
}
if consumer.ClaimsToHeaders != nil {
claimsToHeader(rawClaims, *consumer.ClaimsToHeaders)
}
return nil
}
func deniedJWTMissing() types.Action {
_ = proxywasm.SendHttpResponseWithDetail(401, "jwt-auth.token_missing", WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by JWT Auth check. JWT is missing."), -1)
return types.ActionContinue
}
func deniedJWTExpired() types.Action {
_ = proxywasm.SendHttpResponseWithDetail(401, "jwt-auth.token_expired", WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by JWT Auth check. JWT is expired."), -1)
return types.ActionContinue
}
func deniedJWTVerificationFails() types.Action {
_ = proxywasm.SendHttpResponseWithDetail(401, "jwt-auth.verification_failed", WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by JWT Auth check. JWT verification fails"), -1)
return types.ActionContinue
}
func deniedUnauthorizedConsumer() types.Action {
_ = proxywasm.SendHttpResponseWithDetail(403, "jwt-auth.unauthorized_customer", WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by JWT Auth check. Unauthorized consumer."), -1)
return types.ActionContinue
}
func deniedNotAllow() types.Action {
_ = proxywasm.SendHttpResponseWithDetail(403, "jwt-auth.not_allowed_by_default", WWWAuthenticateHeader(protectionSpace),
[]byte("Request denied by JWT Auth check. JWT token not allow."), -1)
return types.ActionContinue
}
func authenticated(name string) types.Action {
_ = proxywasm.AddHttpRequestHeader("X-Mse-Consumer", name)
return types.ActionContinue
}
func WWWAuthenticateHeader(realm string) [][2]string {
return [][2]string{
{"WWW-Authenticate", fmt.Sprintf("JWT realm=%s", realm)},
}
}