在服务网格中配置JWT(JSON Web Token)请求鉴权,可以实现来源认证,又称为最终用户认证。在接收用户请求时,该配置用于认证请求头信息中的Access Token是否可信,并授权给来源合法的请求。本文介绍如何在ASM中对服务进行JWT请求鉴权。
前提条件
- 请确保Istio版本≥1.6,否则将不支持RequestAuthentication功能。
- 已创建至少一个ASM实例,并添加至少一个ACK集群到该实例中。详情请参见添加集群到ASM实例。
背景信息
服务网格包含两种认证方式:
- 传输认证:基于双向TLS技术,常用于服务间通信认证。
- 来源认证:基于JWT技术,常用于客户端和服务之间的请求认证。
本文介绍的JWT请求授权正是为了实现来源认证,JWT是一种用于双方之间传递安全信息的表述性声明规范。JWT的相关资料请参见JWT官方文档。
步骤一:部署示例服务
- 登录ASM控制台。
- 在左侧导航栏,选择。
- 在网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理。
- 在网格详情页面左侧导航栏选择,然后在右侧页面单击新建。
- 在新建命名空间面板中,填写名称和标签。名称为
foo
,标签为istio-injection:enabled
。表示创建命名空间为foo
,并启用istio-injection
。
- 单击确定,完成
foo
的命名空间创建。
- 在本地执行以下命令,部署官方示例服务
httpbin
和sleep
。kubectl \
--kubeconfig "$USER_CONFIG" \
-n foo \
apply -f "$ISTIO_HOME"/samples/httpbin/httpbin.yaml
kubectl \
--kubeconfig "$USER_CONFIG" \
-n foo \
apply -f "$ISTIO_HOME"/samples/sleep/sleep.yaml
- 执行如下命令,从而实现在Pod就绪前系统会一直等待。
kubectl --kubeconfig "$USER_CONFIG" -n foo get po
kubectl --kubeconfig "$USER_CONFIG" -n foo wait --for=condition=ready pod -l app=httpbin
kubectl --kubeconfig "$USER_CONFIG" -n foo wait --for=condition=ready pod -l app=sleep
执行结果:在sleep
容器内,编辑YAML,验证是否可以请求httpbin
。验证结果http_code
值为200即请求成功。
sleep_pod=$(kubectl --kubeconfig "$USER_CONFIG" get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name})
RESULT=$(kubectl \
--kubeconfig "$USER_CONFIG" \
exec "$sleep_pod" -c sleep -n foo -- curl http://httpbin.foo:8000/ip -s -o /dev/null -w "%{http_code}")
if [[ $RESULT != "200" ]]; then
echo "http_code($RESULT) should be 200"
exit
fi
步骤二:创建请求认证
- 在网格详情页面左侧导航栏选择,然后在右侧页面单击使用YAML创建。
- 设置命名空间为foo,然后在文本框中输入请求身份认证的YAML内容。
jwt-example.yaml示例如下:
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
name: "jwt-example"
namespace: foo
spec:
selector:
matchLabels:
app: httpbin
jwtRules:
- issuer: "testing@secure.istio.io"
jwks: '{ "keys":[ {"e":"AQAB","kid":"DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ","kty":"RSA","n":"xAE7eB6qugXyCAG3yhh7pkDkT65pHymX-P7KfIupjf59vsdo91bSP9C8H07pSAGQO1MV_xFj9VswgsCg4R6otmg5PV2He95lZdHtOcU5DXIg_pbhLdKXbi66GlVeK6ABZOUW3WYtnNHD-91gVuoeJT_DwtGGcp4ignkgXfkiEm4sw-4sfb4qdt5oLbyVpmW6x9cfa7vs2WTfURiCrBoUqgBo_-4WTiULmmHSGZHOjzwa8WtrtOQGsAFjIbno85jp6MnGGGZPYZbDAa_b3y5u-YpW7ypZrvD8BgtKVjgtQgZhLAGezMt0ua3DRrWnKqTZ0BJ_EyxOGuHJrLsn00fnMQ"}]}'
说明
这段YAML实现的策略为:当请求httpbin
服务时,需匹配jwtRules
中定义的规则,即请求头中如果包含Access Token信息,解码后的iss
的值必须为testing@secure.istio.io
。jwks
中定义了Token生成的相关信息,详情请参见JWT官方文档。
- 单击创建。
执行结果:请求头中包含合法的Access Token时返回状态码为200,否则返回状态码为401。
- 返回状态码为200如下所示。
for ((i = 1; i <= 10; i++)); do
RESULT=$(kubectl \
--kubeconfig "$USER_CONFIG" \
exec "$sleep_pod" \
-c sleep \
-n foo \
-- curl "http://httpbin.foo:8000/headers" \
-s \
-o /dev/null \
-w "%{http_code}")
if [[ $RESULT != "200" ]]; then
echo "http_code($RESULT) should be 200"
exit
fi
done
- 返回状态码为401如下所示。
for ((i = 1; i <= 5; i++)); do
RESULT=$(kubectl \
--kubeconfig "$USER_CONFIG" \
exec "$sleep_pod" \
-c sleep \
-n foo \
-- curl "http://httpbin.foo:8000/headers" \
-s \
-o /dev/null \
-H "Authorization: Bearer invalidToken" \
-w "%{http_code}")
if [[ $RESULT != "401" ]]; then
echo "http_code($RESULT) should be 401"
exit
fi
done
步骤三:创建JWT授权策略
- 在网格详情页面左侧导航栏选择,然后在右侧页面单击使用YAML创建。
- 设置命名空间为foo,然后在文本框中输入授权策略的YAML内容。
require-jwt.yaml示例如下:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: require-jwt
namespace: foo
spec:
selector:
matchLabels:
app: httpbin
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"]
说明
这段YAML实现的策略为:请求httpbin
服务时,只有请求头Token解码后,符合iss
的值+/
+sub
的值(即source.requestPrincipals
)为testing@secure.istio.io/testing@secure.istio.io
,请求权限才为ALLOW
。
- Token信息如下所示。
TOKEN='eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjQ2ODU5ODk3MDAsImZvbyI6ImJhciIsImlhdCI6MTUzMjM4OTcwMCwiaXNzIjoidGVzdGluZ0BzZWN1cmUuaXN0aW8uaW8iLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.CfNnxWP2tcnR9q0vxyxweaF3ovQYHYZl82hAUsn21bwQd9zP7c-LS9qd_vpdLG4Tn1A15NxfCjp5f7QNBUo-KC9PJqYpgGbaXhaGx7bEdFWjcwv3nZzvc7M__ZpaCERdwU7igUmJqYGBYQ51vr2njU9ZimyKkfDe3axcyiBZde7G6dabliUosJvvKOPcKIWPccCgefSj_GNfwIip3-SsFdlR7BtbVUcqR-yv-XOxJ3Uc1MI0tz3uMiiZcyPV7sNCU4KRnemRIMHVOfuvHsU60_GhGbiSFzgPTAa9WTltbnarTbxudb_YEOx12JiwYToeX0DCPb43W1tzIBxgm8NxUg'
- 解析Token如下所示。
echo $TOKEN | cut -d '.' -f2 - | base64 --decode -
- 解析Token的输出信息如下所示。
{"exp":4685989700,"foo":"bar","iat":1532389700,"iss":"testing@secure.istio.io","sub":"testing@secure.istio.io"}
JWT官网也提供了同样的解析Token能力,图形化输出如下图所示。

- 单击创建。
执行结果:请求头中包含合法的Access Token时返回状态码为200,否则返回状态码为403。
- 返回状态码为200如下所示。
for ((i = 1; i <= 10; i++)); do
RESULT=$(kubectl \
--kubeconfig "$USER_CONFIG" \
exec "$sleep_pod" \
-c sleep \
-n foo \
-- curl "http://httpbin.foo:8000/headers" \
-s \
-o /dev/null \
-H "Authorization: Bearer $TOKEN" \
-w "%{http_code}")
if [[ $RESULT != "200" ]]; then
echo "http_code($RESULT) should be 200"
exit
fi
done
- 返回状态码为403如下所示。
for ((i = 1; i <= 10; i++)); do
RESULT=$(kubectl \
--kubeconfig "$USER_CONFIG" \
exec "$sleep_pod" \
-c sleep \
-n foo \
-- curl "http://httpbin.foo:8000/headers" \
-s \
-o /dev/null \
-w "%{http_code}")
if [[ $RESULT != "403" ]]; then
echo "http_code($RESULT) should be 403"
exit
fi
done
步骤四:追加JWT授权策略
- 在网格详情页面左侧导航栏选择。
- 在授权策略页面单击require-jwt操作列的YAML。
- 在编辑面板中追加补充以下内容。
require-jwt-group.yaml补充的内容。
when:
- key: request.auth.claims[groups]
values: ["group1"]
以下为完整的require-jwt-group.yaml示例。
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: require-jwt
namespace: foo
spec:
selector:
matchLabels:
app: httpbin
action: ALLOW
rules:
- from:
- source:
requestPrincipals: ["testing@secure.istio.io/testing@secure.istio.io"]
when:
- key: request.auth.claims[groups]
values: ["group1"]
说明
这段YAML实现的策略为:请求httpbin
服务时,只有请求头Token解码后,符合groups
的值包含group1,请求权限才为ALLOW
。
- Token信息如下所示。
TOKEN_GROUP='eyJhbGciOiJSUzI1NiIsImtpZCI6IkRIRmJwb0lVcXJZOHQyenBBMnFYZkNtcjVWTzVaRXI0UnpIVV8tZW52dlEiLCJ0eXAiOiJKV1QifQ.eyJleHAiOjM1MzczOTExMDQsImdyb3VwcyI6WyJncm91cDEiLCJncm91cDIiXSwiaWF0IjoxNTM3MzkxMTA0LCJpc3MiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyIsInNjb3BlIjpbInNjb3BlMSIsInNjb3BlMiJdLCJzdWIiOiJ0ZXN0aW5nQHNlY3VyZS5pc3Rpby5pbyJ9.EdJnEZSH6X8hcyEii7c8H5lnhgjB5dwo07M5oheC8Xz8mOllyg--AHCFWHybM48reunF--oGaG6IXVngCEpVF0_P5DwsUoBgpPmK1JOaKN6_pe9sh0ZwTtdgK_RP01PuI7kUdbOTlkuUi2AO-qUyOm7Art2POzo36DLQlUXv8Ad7NBOqfQaKjE9ndaPWT7aexUsBHxmgiGbz1SyLH879f7uHYPbPKlpHU6P9S-DaKnGLaEchnoKnov7ajhrEhGXAQRukhDPKUHO9L30oPIr5IJllEQfHYtt6IZvlNUGeLUcif3wpry1R5tBXRicx2sXMQ7LyuDremDbcNy_iE76Upg'
- 解析Token如下所示。
echo "$TOKEN_GROUP" | cut -d '.' -f2 - | base64 --decode - | jq
- 解析Token的输出信息如下所示。
{
"exp": 3537391104,
"groups": [
"group1",
"group2"
],
"iat": 1537391104,
"iss": "testing@secure.istio.io",
"scope": [
"scope1",
"scope2"
],
"sub": "testing@secure.istio.io"
}
- 单击确定。
执行结果:请求头中包含合法的Access Token时返回状态码为200,否则返回状态码为403。
- 返回状态码为200如下所示。
for ((i = 1; i <= 10; i++)); do
RESULT=$(kubectl \
--kubeconfig "$USER_CONFIG" \
exec "$sleep_pod" \
-c sleep \
-n foo \
-- curl "http://httpbin.foo:8000/headers" \
-s \
-o /dev/null \
-H "Authorization: Bearer $TOKEN_GROUP" \
-w "%{http_code}")
if [[ $RESULT != "200" ]]; then
echo "http_code($RESULT) should be 200"
exit
fi
done
- 返回状态码为403如下所示。
for ((i = 1; i <= 10; i++)); do
RESULT=$(kubectl \
--kubeconfig "$USER_CONFIG" \
exec "$sleep_pod" \
-c sleep \
-n foo \
-- curl "http://httpbin.foo:8000/headers" \
-s \
-o /dev/null \
-H "Authorization: Bearer $TOKEN" \
-w "%{http_code}")
if [[ $RESULT != "403" ]]; then
echo "http_code($RESULT) should be 403"
exit
fi
done