ID Token - JWT

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ID Token - JWT相关的知识,希望对你有一定的参考价值。

参考技术A

我们来继续前两章( OAuth2 总结 , 对OpenID Connect的理解 )的讨论,进入对JWT的理解。先来简单回顾一下OAuth2和OpenID:

OpenID建立在OAuth之上,完成了认证和授权。而认证和授权的结果就体现在这个ID token之上,而这个ID token通常会是JWT。那么为什么会是JWT呢?我们通过以下几点来逐一解释。

JWT RFC 7519 给出了官方定义的一些字段:

当然也不限于此,可以根据自己的需要,添加其他字段。到此,我们可以看出JWT提供了ID token所需要的能力。一个完整的JTW是由三部分组成:Header,Payload,Signature。三者之间由 . 隔开,刚才提到的认证授权的字段就存在于Payload中。
Header中存放的是JTW的元数据,包含签名的算法以及token的类型,如下所示:

Signature部分是对前两部分的签名, 防止数据篡改 。首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

算出签名以后,把 Header、Payload、Signature三个部分经过base64序列化为三个字符串,再讲三个字符串由 . 为间隔拼接成一个字符串返回给客户端。

ID Token需要有足够的安全性,JWT是如何做到的呢?
刚看到了签名部分,签名的作用只是为了防止数据篡改,而JWT默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。比如认证服务器中使用私钥进行加密,把公钥分发给其他应用服务器,应用服务拿到加密后的token后用公钥解密,获取到JWT,再用签名的秘钥来验证数据是否经过的篡改。

我们来看看还有神马其他备选么?Simple Web Tokens (SWT)和Security Assertion Markup Language Tokens (SAML)。
JWT vs SAML:JWT基于json,而SAML基于XML,在大小上就有足够的优势,更适用于html和HTTP。
JWT vs SWT:在安全性上,SWT只支持对称加密,而JWT和SAML支持公私钥的加密方式。

作为一个mobile developer,也想在这里对比一下原先的简单token模式和SSO中的JWT:
在没有该机制前,我们通常会使用一个随机产生的字符串作为token,认证成功后返回给前端,之后前端的每个请求带上这个token。若后台服务的是个单体应用没有什么问题,请求来了,验证一下token是否有效即可,但当认证服务和其他的应用服务是分离的,怎么做呢?应用服务受到请求,再向认证服务发起一个请求来验证验证token是否合法,是否有权限访问该应用服务。这样做到没有什么问题,只是当服务变多时,比如微服务下,势必会造成认证服务器的压力过大。
在使用该机制后,客户端通过认证服务登录,获得这个JWT,之后其他应用服务自身便可以验证这个token的是否有效,是否有权访问。

看似完美,但也有它自身的问题,我们来看一个场景:权限变更。某用户原先是一个超级管理员,可以访问所有服务,并可进行任意的删除,更改操作,他在这个状态下拿到了JWT。随后,由于权限更改为普通管理员,便不应该具有所有权限,但此时他开始时的JWT被缓存在客户端仍然可用,其他应用服务也并无法知道这个用户的权限已经被更改,后果可想而知了。解决的方式无非是将这个token的有效时间设置的短一些。

如何验证 Microsoft jwt id_token?

【中文标题】如何验证 Microsoft jwt id_token?【英文标题】:How do I validate a Microsoft jwt id_token? 【发布时间】:2020-03-27 19:29:19 【问题描述】:

我正在使用从 Microsoft 到客户端的 jwt 令牌来验证来自它的请求到 Web API(服务器)。我可以控制客户端 (js) 和服务器 (Python) 的代码。

在客户端,我使用以下请求来获取令牌(用户通过租户上的密码/2FA 声明):

`https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/authorize
            ?response_type=id_token+token
            &client_id=$CLIENT_ID
            &redirect_uri=$redirect_uri
            &scope=openid+email+profile
            &state=$guid()
            &nonce=$guid()`

这里guid是唯一值,TENANT_ID是租户,CLIENT_ID是客户端。

得到这个令牌后,我将它作为授权标头发送,如下所示:

init = 
    headers: 
       'Authorization': `Bearer $token`,
    


return fetch(url, init).then(response => 
    return response.json()
)

然后,我在服务器上检索令牌并验证它:

if 'Authorization' in request.headers and request.headers['Authorization'].startswith('Bearer '):
    token = request.headers['Authorization'][len('Bearer '):]
    from authlib.jose import jwt
    claims = jwt.decode(token, jwk)

其中jwkhttps://login.microsoftonline.com/TENANT_ID/discovery/v2.0/keys 的内容。

整个流程一直有效,直到验证失败,并出现以下错误:

authlib.jose.errors.InvalidHeaderParameterName: invalid_header_parameter_name: Invalid Header Parameter Names: nonce

这表示令牌的头部包含一个密钥nonce(我验证过)。

查看 Microsoft 的相关文档 here,标头上没有对 nonce 的引用——只是在有效负载上。

Q1:我在这里做错了什么?

Q2:假设 Microsoft 是将 nonce 放在错误位置的人(标头而不是有效负载),是否可以在将 nonce 传递给 jose 的身份验证库之前从标头(在服务器端)中删除它?这样做安全吗?

【问题讨论】:

【参考方案1】:

见:https://robertoprevato.github.io/Validating-JWT-Bearer-tokens-from-Azure-AD-in-Python/

这是我在 API 中验证 Azure AD Id_tokens 的方法:

import base64
import jwt
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization


def ensure_bytes(key):
    if isinstance(key, str):
        key = key.encode('utf-8')
    return key


def decode_value(val):
    decoded = base64.urlsafe_b64decode(ensure_bytes(val) + b'==')
    return int.from_bytes(decoded, 'big')


def rsa_pem_from_jwk(jwk):
    return RSAPublicNumbers(
        n=decode_value(jwk['n']),
        e=decode_value(jwk['e'])
    ).public_key(default_backend()).public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )


# obtain jwks as you wish: configuration file, HTTP GET request to the endpoint returning them;
jwks = 
    "keys": [
        
            "kty": "RSA",
            "use": "sig",
            "kid": "piVlloQDSMKx...",
            "x5t": "piVlloQDSMKx...",
            "n": "0XhhwpmEpN-jDBapnzhF...",
            "e": "AQAB",
            "x5c": [
                "MIIDBTCCAe2gAwIBAgIQMCJcg...."
            ],
            "issuer": "https://login.microsoftonline.com/tenant/v2.0"
        
    ]


# configuration, these can be seen in valid JWTs from Azure B2C:
valid_audiences = ['dd050a67-ebfd-xxx-xxxx-xxxxxxxx'] # id of the application prepared previously

class InvalidAuthorizationToken(Exception):
    def __init__(self, details):
        super().__init__('Invalid authorization token: ' + details)


def get_kid(token):
    headers = jwt.get_unverified_header(token)
    if not headers:
        raise InvalidAuthorizationToken('missing headers')
    try:
        return headers['kid']
    except KeyError:
        raise InvalidAuthorizationToken('missing kid')



def get_jwk(kid):
    for jwk in jwks.get('keys'):
        if jwk.get('kid') == kid:
            return jwk
    raise InvalidAuthorizationToken('kid not recognized')

def get_issuer(kid):
    for jwk in jwks.get('keys'):
        if jwk.get('kid') == kid:
            return jwk.get('issuer')
    raise InvalidAuthorizationToken('kid not recognized')

def get_public_key(token):
    return rsa_pem_from_jwk(get_jwk(get_kid(token)))


def validate_jwt(jwt_to_validate):
    try:
        public_key = get_public_key(jwt_to_validate)
    issuer = get_issuer(kid)


        options = 
            'verify_signature': True,
            'verify_exp': True,  # Skipping expiration date check
            'verify_nbf': False,
            'verify_iat': False,
            'verify_aud': True  # Skipping audience check
        

        decoded = jwt.decode(jwt_to_validate,
                             public_key,
                             options=options,
                             algorithms=['RS256'],
                             audience=valid_audiences,
                             issuer=issuer)

        # do what you wish with decoded token:
        # if we get here, the JWT is validated
        print(decoded)

    except Exception as ex:
        print('The JWT is not valid!')
        return False
    else:
        return decoded
        print('The JWT is valid!')

【讨论】:

@chatzich 见docs.microsoft.com/en-us/azure/active-directory-b2c/… 格式:https://tenant_name.b2clogin.com/tenant_name.onmicrosoft.com/policy_name/discovery/v2.0/keys 例如:https://contoso.b2clogin.com/contoso.onmicrosoft.com/b2c_1_signupsignin1/discovery/v2.0/keys

以上是关于ID Token - JWT的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET Core 自动刷新JWT Token

为啥 JWT Token 包含用于加密/签名的算法

JWT Token 访问 AuthenticatedUser

如何根据用户输入在 Jwt Token 上添加自定义声明?

cookiesession,token,还在傻傻分不清?

Session/Cookie/Token还傻傻分不清?