公钥获取和令牌验签
公钥获取和验签
Section titled “公钥获取和验签”企业服务平台的token,可以在应用系统的本地进行验签,这样的可以提升接口性能,不需要每次都进行验签。方案如下:
1、公钥获取;
公钥获取,做好缓存机制,下文有介绍,不需要频繁调用;
2、令牌验签;
使用平台公钥进行令牌验签,下文有介绍,可以加缓存机制;
请求地址: /.well-known/openid-configuration
{ "issuer": "https://esp.xkw.cn", "authorization_endpoint": "https://esp.xkw.cn/oauth2/authorize", "token_endpoint": "https://esp.xkw.cn/oauth2/token", "token_endpoint_auth_methods_supported": [ "client_secret_post" ], "jwks_uri": "https://esp.xkw.cn/oauth2/jwks", "response_types_supported": [ "code" ], "grant_types_supported": [ "authorization_code", "client_credentials", "refresh_token" ], "code_challenge_methods_supported": [ "S256" ], "id_token_signing_alg_values_supported": [ "RS256" ]}请求地址: /oauth2/jwks
{ "keys": [ { "kty": "RSA", "kid": "380ce45a", "use": "sig", "alg": "RS256", "n": "6Zlo3NEfG5Nv1PFWxRLqYMbLSyHAk6MN1x5mCRttqdziah6V8jKPOqrzOCqQPvF9zJw5U7ghBgdGkPQs_w_yfR2PZHM4C1x7dFSgaSKRRG3vpiL5nSwu9qTfzdCo7OG8xhX5eL2P-5Uish6h5LiAWO_uOflizR-sVRC4dONCC_MSjtGptVlZJGmpA-Vr-aFqgzmyaX2oMpxwj7CJ0bx0fSBMhgRJ5xdlWl4wV3OWIDiQs9C2Y0dtF957b0RQ_YUyigfAg0SRpaAXVGt10L8ysHIj52h8men4wL7CWraZscNj_2NFdt4g5ERbihduuvNfdQ3BB9z74QIOn3n1vsRxBQ==", "e": "AQAB" }, { "kty": "RSA", "kid": "e98929a1", "use": "sig", "alg": "RS256", "n": "04csr2R7dcF2xX2zlB7hX4EqD0lmoHxGDSKWdh3rKEtZUT7aN8v-dJnNI5S2zk_I8mbtQAIYUOhJR7XaEgHl2KuHeQrgN0--W-Id1Io9E7JPSVV3nzIFnLK2v2ZndRfBegHGtHLBSd14Pvwnq-gyiWhAcnYSKr0doL_LhaullgDXZjY6x700EFoKpoQjmPHFIuN-1q_u41XD4GEyFd1wmVifTR_ObcpC1JdXQpWLd3ejiaq9HPsgIDN7eC3mGWQVxpDKEaXeSNkqGKnjWzYR5FNYSX3QZNOzOZf6azN-1qDxWx_25eOnb3w7QK9kuqi_pNsnVUGz5Dee_wo9p31x2w==", "e": "AQAB" } ]}公钥缓存机制
Section titled “公钥缓存机制”应用系统API,接口提供方需要定期获取公钥配置,可以在应用系统启动时获取最新,后续可以定时任务进行更新可以两小时更新一次;
注意:不要频繁请求获取公钥,以免被限流控制;
令牌签名验证
Section titled “令牌签名验证”平台公钥实例化
Section titled “平台公钥实例化”根据公钥接口获取最新公钥配置,实例化公钥实例,这个不同的开发语言写法不一样,可以参考网上文章,下面为java的例子代码:
public static PublicKey generatePublicKey(String base64Modulus, String base64Exponent) { try { // Base64 解码模数和指数 byte[] modulusBytes = Base64.getUrlDecoder().decode(base64Modulus); byte[] exponentBytes = Base64.getUrlDecoder().decode(base64Exponent);
// 将字节数组转换为 BigInteger,在最前面加个0,防止负数 BigInteger modulus = new BigInteger(1, modulusBytes); BigInteger exponent = new BigInteger(1, exponentBytes);
// 创建 RSAPublicKeySpec RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
// 获取 KeyFactory 实例并生成 PublicKey KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePublic(spec); } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { e.printStackTrace(); return null; } }令牌内容验证
Section titled “令牌内容验证”使用公钥实例,结合jwt第三方库(nimbus-jose-jwt)进行签名验证,不同语言写法不一样,可以参考网上文章,下面为java的例子代码:
SignedJWT jwt = SignedJWT.parse(token);JWSHeader jwtHeader = jwt.getHeader();// 平台公钥配置反序列化对象Jwkfor(Jwk jwk:keys){ // 比对公钥kid,使用正确的公钥 if(jwk.getKid().equals(jwtHeader.getKeyID())){ // 使用公钥配置中的n(模数)和e(指数)实例化RSAPublicKey PublicKey publicKey = PublicKeyGenerator.generatePublicKey(jwk.getN(),jwk.getE()); // 生成验证对象 RSASSAVerifier verifier = new RSASSAVerifier((RSAPublicKey)publicKey); boolean verify = jwt.verify(verifier); if (!verify) { throw new RuntimeException("jwt验签失败, invalid token: " + token); }else{ JWTClaimsSet jwtClaimsSet = jwt.getJWTClaimsSet(); String subject = jwtClaimsSet.getSubject(); System.out.println("jwt验签成功, 调用方: " + subject); List<String> scopes = jwtClaimsSet.getStringListClaim("scope"); if(scopes != null && scopes.contains("bi")){ System.out.println("jwt验签成功, 允许对bi接口访问"); }else{ throw new RuntimeException("jwt验签成功, 没有授权,无法调用"); } // 获取过期时间 Date expirationTime = jwt.getJWTClaimsSet().getExpirationTime(); if (expirationTime != null) { // 检查 token 是否已经过期 boolean isExpired = new Date().after(expirationTime); if (isExpired) { throw new RuntimeException("jwt验签成功, 令牌已过期"); } else { //接口自身业务逻辑处理 } } } }}令牌缓存机制
Section titled “令牌缓存机制”第一次使用平台公钥验证通过后,存储在缓存中,缓存的过期时间使用令牌的过期时间,后面调用,取缓存,如下:
| 缓存KEY | 缓存VALUE | 过期时间 |
|---|---|---|
| 令牌token | { “sub”: “oa”, “aud”: “oa”, “nbf”: 1710491527, “scope”: [“bi”, “sales”], “iss”: “https://uat-esp.xkw.cn”, “exp”: 1710491827, “iat”: 1710491527 } | 令牌的exp |