istio 1.10学习笔记14: 使用istio实现http服务的JWT身份认证
2021-08-01
前面一节初步学习了istio安全管理功能中的认证策略,并使用认证策略配置了服务之间的双向TLS,使用认证策略对暴露到集群外部的http服务开启了基于JWT的终端用户认证。本节将对上节配置JWT终端用户认证时用到一些JWT相关知识做一个补充学习。
JWT即JSON Web Token,是一种用于产生访问令牌的开放标准(rfc7519)。 JWT token被设计为紧凑且安全的,特别适用于实现分布式服务的认证方案,适用于实现跨域认证方案。
JWKS(JSON Web Key Set)和JWK(JSON Web Key) #
之前在使用istio配置JWT终端用户认证时,创建的认证策略中用到了jwksUri
字段:
1apiVersion: security.istio.io/v1beta1
2kind: RequestAuthentication
3metadata:
4 name: "jwt-example"
5 namespace: istio-system
6spec:
7 selector:
8 matchLabels:
9 istio: ingressgateway
10 jwtRules:
11 - issuer: "[email protected]"
12 jwksUri: "https://raw.githubusercontent.com/istio/istio/release-1.10/security/tools/jwt/samples/jwks.json"
13EOF
JWKS是一个json文件:
1{
2 "keys":[
3 {
4 "e":"AQAB",
5 "kid":"DHFbpoIUqrY8t2zpA2qXfCmr5VO5ZEr4RzHU_-envvQ",
6 "kty":"RSA",
7 "n":"xAE7eB6qugXyCAG3yhh7pkD..."
8 }
9 ]
10}
JWKS即JWK Set,也就是一组JWK。那么什么是JWK呢?JWK即JSON Web Key是JWT的秘钥,描述了一个加密密钥(公钥或私钥)的值及其各项属性。 JWKS就是一组JWK密钥。Istio就是使用JWKS提供的密钥信息对JWT token进行签名验证的。JWKS的JSON文件格式如下:
1{
2"keys": [
3 <jwk1>,
4 <jwk2>,
5 ...
6]}
使用jwx命令行工具生成JWK #
那么怎么生成JWK呢?这里使用https://github.com/lestrrat-go/jwx这个命令行工具。 jwx是一个用go语言开发的命令行工具,内置了对各种JWx(JWT, JWK, JWA, JWS, JWE)的支持。
使用go get
安装jwx命令行工具:
1go get -u -v github.com/lestrrat-go/jwx/cmd/jwx
下面演示使用jwx命令行工具生成一个JWK,通过模板指定kid为myawesomekey:
1jwx jwk generate --keysize 4096 --type RSA --template '{"kid":"myawesomekey"}' -o rsa.jwk
2cat rsa.jwk
3{
4 "d": "grRsO6jTvTun5cnpBNf...",
5 "dp": "8RUd5gEJAqxo7kMqdEFkeQu3....",
6 "dq": "SQ2Xh8iou8Qd-THxPXKfTJDnI...",
7 "e": "AQAB",
8 "kid": "myawesomekey",
9 "kty": "RSA",
10 "n": "wIUzbQ8-WJ_u4rmDAIASBLODKlR7TMBYo...",
11 "p": "9Ka47wKNibYmGtImrbXeLkbSngUaWXMWf2...",
12 "q": "yXNqB6DeuFWWy9eyb_Ab6VaqdeeqclmpHT...",
13 "qi": "hd4_FexqN4rpbihDRGmG_WdnE4Uwnyuqv..."
14}
从rsa.jwk中提取JWK公钥:
1jwx jwk fmt --public-key -o rsa-public.jwk rsa.jwk
2cat rsa-public.jwk
3{
4 "e": "AQAB",
5 "kid": "myawesomekey",
6 "kty": "RSA",
7 "n": "sptztCJ....."
8}
上面生成的JWK其实就是RSA公钥私钥的换了一种存储格式而已,下面演示如何将它们转换成PEM格式的公钥和私钥:
1jwx jwk fmt -I json -O pem rsa.jwk
2-----BEGIN PRIVATE KEY-----
3MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQCym3O0Ik5QGZ8i
4......
5-----END PRIVATE KEY-----
6
7jwx jwk fmt -I json -O pem rsa-public.jwk
8-----BEGIN PUBLIC KEY-----
9MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsptztCJOUBmfIqSE8LR5
10......
11-----END PUBLIC KEY-----
使用jwx命令行签发JWT Token并验证有效性 #
签发一个JWT Token:
1jwx jws sign --key rsa.jwk --alg RS256 --header '{"typ":"JWT"}' -o token.txt - <<EOF
2{
3 "iss": "[email protected]",
4 "sub": "john007",
5 "iat": 1628138793,
6 "exp": 1629138793,
7 "name": "John Doe"
8}
9EOF
10
11cat token.txt
12eyJhbGciOiJSUzI1NiIsImtpZCI6Im15YXdlc29tZWtleSIsInR5cCI6IkpXVCJ9......
上面生成JWT Token看起来是这样的:
1eyJhbGU省略部分字符CI6IkpXVCJ9.ewogICJpc3MiOiA省略部分字符hbWUiOiAiSmUiCn0K.jUgIDUhHJ4Aaf省略部分字符WVvVGhNvkhXs
实际上是由下面的算法生成的:
1base64url_encode(Header) + '.' + base64url_encode(Claims) + '.' + base64url_encode(Signature)
可以使用jwx命令行工具将jwt token中的header, claims(payload), signature解析出来:
1jwx jws parse token.txt
2
3Signature: "jUgIDUhHJ4A..."
4Protected Headers: "eyJhbGciOiJSUzI1..."
5Decoded Protected Headers: {
6 "alg": "RS256",
7 "kid": "myawesomekey",
8 "typ": "JWT"
9 }
10Payload: {
11 "iss": "[email protected]",
12 "sub": "john007",
13 "iat": 1628138793,
14 "exp": 1629138793,
15 "name": "John Doe"
16 }
先看一下Headers部分,包含了一些元数据,注意type
和alg
是必须的:
type
: token的类型,JWT
表示JWT类型的tokenalg
: 所使用的签名算法,这里是RSA256kid
: JWK的kid
再看一下Payload(Claims)部分,payload包含了这个token的数据信息,JWT标准规定了一些字段,另外还可以加入一些承载额外信息的字段。
iss
: issuer,token是谁签发的sub
: token的主体信息,一般设置为token代表用户身份的唯一id或唯一用户名exp
: token过期时间,Unix时间戳格式iat
: token 创建时间, Unix时间戳格式jti
: 当前token的唯一标识
最后看一下签名Signature信息,签名是基于JSON Web Signature (JWS)标准来生成的,签名主要用于验证token是否有效,是否被篡改。 签名支持很多种算法,这里使用的是RSASHA256,具体的签名算法如下:
1RSASHA256(
2 base64UrlEncode(header) + "." +
3 base64UrlEncode(payload),
4 <rsa-public-key>,
5 <rsa-private-key>
最后使用RSA Public Key验证JWT Token的有效性:
1jwx jws verify --alg RS256 --key rsa-public.jwk token.txt
2{
3 "iss": "[email protected]",
4 "sub": "john007",
5 "iat": 1628138793,
6 "exp": 1629138793,
7 "name": "John Doe"
8}
在istio认证策略中配置JWT终端用户认证 #
有了前面对JWT,JWK,JWKS概念的学习,下面配置istio的认证策略使用我们自己创建的JWKS。
1apiVersion: "security.istio.io/v1beta1"
2kind: "RequestAuthentication"
3metadata:
4 name: "jwt-example"
5 namespace: istio-system
6spec:
7 selector:
8 matchLabels:
9 istio: ingressgateway
10 jwtRules:
11 - issuer: "[email protected]"
12 forwardOriginalToken: true
13 jwks: |
14 {
15 "keys": [
16 {
17 "e": "AQAB",
18 "kid": "myawesomekey",
19 "kty": "RSA",
20 "n": "tZdEjpwtPlRHRFqdUGX6zgCht0rT5hrbs1iXzHKJ8XIJiqgCS6HYCSR9F8ziHfW5fmhftGHmmhQxw1eXou0olIcSGHQsLjkravorvr6vMcNU4OmX48CZzVxhUMBWStbNhHOlOCMKxPnHmT4tlyuv9CwgQ0zI8_qlCpWKhlSVjTo92VRVVFHnhMAS0garwIyHmv_nHLovrdFz9fQtHPQwxvEolylnCfPANGhNJsdYbMl-7lONyB7gb_ymrc7ykt372cYEBIF419b-MIT9Sl7_hL_e4Qyw565pbipqGUDhXHSiDpTI_8Y8gK-Gev7VZu_T-BBME9VO0n00gSHIASUlw-EISnvSFrNUXCi05RN2EfH48Elc0Og9eL5OHxrXLQV6FG4pPBiR2Umx7a4rqp5X5fjbCVe0lRYL0kU7neW02n3UWItbF1oSFLL4SPQmRTMZBrECcxiq3RF51pPwBM-qAGH8KAxY3tgO_xefe8Wd3BSFZHFF_xiEGb2G0W43osFStPKQ0e7TvX2FVlP7XSpQ3Ym9FrRfKolEY7bQv2YpeCjOycFMVUR1geazEiVEJbwCrJIMQPbTI9S3_gyOBknoR34kiY52nJeLgA_jwneS4yWLVi6OESZjrgF12Kjh5Y78gwQEQD0EHJ1T9VQrdDOWtvqnK2H6HF3vcr1XyxpruQs"
21 }
22 ]
23 }
上面的RequestAuthentication中的jwtRules中使用了jwks
字段配置了我们前面创建的JWK公钥。forwardOriginalToken: true
表示启用Authorization
请求头转发,默认情况下,istio在完成了身份验证之后,会在转发的请求中移除Authorization
请求头,为了确保后端服务获取到终端用户信息,可以设置forwardOriginalToken为true。另外注意到配置里的jwtRules
是数组,可以配置多个jwtRule,实际使用中会为不同的jwt issuer配置自己的jwks,每个jwks中有多个jwk。JWK认证规则是根据JWT token payload中的iss
字段匹配到istio中配置的jwks,然后根据JWT token header中的kid
字段在jwks中找到对应的jwk公钥,使用找到的jwk公钥对token验证签名合法性。
上节内容我们还学习到RequestAuthentication
配置的认证策略,默认情况下会忽略不带Authorization
请求头的流量直接放行,需要配置授权策略AuthorizationPolicy
要求必须携带Authorization
请求头。
1apiVersion: security.istio.io/v1beta1
2kind: AuthorizationPolicy
3metadata:
4 name: "frontend-ingress"
5 namespace: istio-system
6spec:
7 selector:
8 matchLabels:
9 istio: ingressgateway
10 action: DENY
11 rules:
12 - from:
13 - source:
14 notRequestPrincipals: ["*"]
15 to:
16 - operation:
17 hosts: ["httpbin.example.com"]
下面使用前面创建的jwk私钥签发一个token,确认使用新签发的token可以访问:
1jwx jws sign --key rsa.jwk --alg RS256 --header '{"typ":"JWT"}' -o token.txt - <<EOF
2{
3 "iss": "[email protected]",
4 "sub": "john007",
5 "iat": 1628138793,
6 "exp": 1629138793,
7 "name": "John Doe"
8}
9EOF
10
11TOKEN=$(cat token.txt) && \
12curl --header "Authorization: Bearer $TOKEN" https://httpbin.example.com/headers -s -o /dev/null -w "%{http_code}\n"
13200
14
15curl --header "Authorization: Bearer errortoken" https://httpbin.example.com/headers -s -o /dev/null -w "%{http_code}\n"
16401
17
18
19curl https://httpbin.example.com/headers -s -o /dev/null -w "%{http_code}\n"
20403
注意上面的测试中,使用签发的合法的token,可以正常访问。 使用错误的token,即RequestsAuthentication验证失败的请求,会返回401状态码。 不带token的请求,即AuthorizationPolicy验证失败的请求,会返回403状态码。