泉州个人建站模板,网站建设规划书范文500字,机械手表网站,网站前端切图做多个页面参考文档#xff1a;spring-authorization-server【版本1.2.2】
问题
在spring-authorization-server官方文档中提供了JWK Set Endpoint相关介绍#xff0c;此端点主要返回JWK Set #xff0c;此JWK Set包含了授权服务提供的所有公钥集#xff0c;具体可通过访问端点spring-authorization-server【版本1.2.2】
问题
在spring-authorization-server官方文档中提供了JWK Set Endpoint相关介绍此端点主要返回JWK Set 此JWK Set包含了授权服务提供的所有公钥集具体可通过访问端点/oauth2/jwks来获取。
但获取到公钥集如何来运用呢如何来验签的呢这是此次我需要了解的问题。
在了解基于private_key_jwt的客户端身份验证方法时我了解到授权服务是如何通过客户端暴露的JWK Set Endpoint来获取到公钥集然后通过拿到的公钥集又是如何验签的解决了我的疑问。
代码梳理
通过梳理源码经层层调用我定位到几个主要的方法。 首先是JwtClientAssertionDecoderFactory#createDecoder(registeredClient)的调用
...
private final MapString, JwtDecoder jwtDecoders new ConcurrentHashMap();Override
public JwtDecoder createDecoder(RegisteredClient registeredClient) {Assert.notNull(registeredClient, registeredClient cannot be null);return this.jwtDecoders.computeIfAbsent(registeredClient.getId(), (key) - {NimbusJwtDecoder jwtDecoder buildDecoder(registeredClient);// 设置相关的校验逻辑参考JwtClientAssertionDecoderFactory#defaultJwtValidatorFactory方法 jwtDecoder.setJwtValidator(this.jwtValidatorFactory.apply(registeredClient));return jwtDecoder;});
}此方法用来往jwtDecoders对象中维护NimbusJwtDecoder相同的客户端ID存在则返回NimbusJwtDecoder对象不存在则创建。
再说NimbusJwtDecoder的创建在创建NimbusJwtDecoder时会执行NimbusJwtDecoder#processor()方法的调用为jwkSource此处的jwkSource为RemoteJWKSet对象对象提供了远程调用JWK Set Endpoint的地址和方法。这样就有了可以获取JWK Set的条件了。
JWTProcessorSecurityContext processor() {// 通过jwkSetRetriever来远程调用获取JWK SetResourceRetriever jwkSetRetriever new RestOperationsResourceRetriever(this.restOperations);// jwkSetUri为客户端暴露的JWK Set端点地址String jwkSetUri this.jwkSetUri.apply(this.restOperations);// 此处获取到的jwkSource为RemoteJWKSetJWKSourceSecurityContext jwkSource jwkSource(jwkSetRetriever, jwkSetUri);ConfigurableJWTProcessorSecurityContext jwtProcessor new DefaultJWTProcessor();// 创建JWSVerificationKeySelector对象并给属性jwkSource、jwsAlgs初始化值jwsAlgs包含一个对象JWSAlgorithm(RS256, Requirement.RECOMMENDED)jwtProcessor.setJWSKeySelector(jwsKeySelector(jwkSource));// Spring Security validates the claim set independent from NimbusjwtProcessor.setJWTClaimsSetVerifier((claims, context) - {});this.jwtProcessorCustomizer.accept(jwtProcessor);return jwtProcessor;
}然后通过调用RemoteJWKSet#updateJWKSetFromURL方法来获取jwkSet。此处的jwkSet对象对应到指定算法的key集合例如如果Key Type对应“RSA”那么返回的jwkSet对象为RSAKey对象集。
private JWKSet updateJWKSetFromURL()throws RemoteKeySourceException {Resource res;try {res jwkSetRetriever.retrieveResource(jwkSetURL);} catch (IOException e) {throw new RemoteKeySourceException(Couldnt retrieve remote JWK set: e.getMessage(), e);}JWKSet jwkSet;try {jwkSet JWKSet.parse(res.getContent());} catch (java.text.ParseException e) {throw new RemoteKeySourceException(Couldnt parse remote JWK set: e.getMessage(), e);}jwkSetCache.put(jwkSet);return jwkSet;
}然后再把jwkSet中所有的JWK对象的publicKey获取到通过SignedJWT#verify来验签。
...
// 获取所有的publicKey集
List? extends Key keyCandidates selectKeys(signedJWT.getHeader(), claimsSet, context);if (keyCandidates null || keyCandidates.isEmpty()) {throw new BadJOSEException(Signed JWT rejected: Another algorithm expected, or no matching key(s) found);
}ListIterator? extends Key it keyCandidates.listIterator();while (it.hasNext()) {// 根据算法返回JWSVerifier对象例如“RS256”返回RSASSAVerifier对象JWSVerifier verifier getJWSVerifierFactory().createJWSVerifier(signedJWT.getHeader(), it.next());if (verifier null) {continue;}// 根据算法获取Signature对象例如“RS256”对应 Signature.getInstance(SHA256withRSA)然后对verifier设置publicKey最后进行验签final boolean validSignature signedJWT.verify(verifier);if (validSignature) {return verifyClaims(claimsSet, context);}if (! it.hasNext()) {// No more keys to try outthrow new BadJWSException(Signed JWT rejected: Invalid signature);}
}
...上面是跟踪代码摘取的部分代码相对比较乱其实如果针对上面的调用把主要的代码逻辑摘取出来就可以构建自己的获取JWK Set和验签的逻辑。
自定义方法
远程调用获取JWK Set
/*** 根据JWK Set Endpoint地址获取JWK集合* * param jwksUrl JWK Set Endpoint地址* return JWK Set*/
public static JWKSet loadJWKSByUrl(String jwksUrl) throws IOException, ParseException {URL jwkSetURL new URL(jwksUrl);ResourceRetriever jwkSetRetriever new DefaultResourceRetriever(RemoteJWKSet.resolveDefaultHTTPConnectTimeout(),RemoteJWKSet.resolveDefaultHTTPReadTimeout(),RemoteJWKSet.resolveDefaultHTTPSizeLimit());Resource res jwkSetRetriever.retrieveResource(jwkSetURL);JWKSet jwkSet JWKSet.parse(res.getContent());return jwkSet;}验签
/*** 根据公钥验证JWTJSON WEB TOKEN** param jwksUrl JWK Set Endpoint地址* param jwt 要验证的JWTJSON WEB TOKEN* return 验证结果true正确false不正确*/public static boolean verifyJWT(String jwksUrl, String jwt) throws ParseException, IOException, JOSEException {Base64URL[] parts JOSEObject.split(jwt);SignedJWT signedJWT new SignedJWT(parts[0], parts[1], parts[2]);JWSHeader jwsHeader JWSHeader.parse(parts[0].decodeToString());JWKMatcher jwkMatcher JWKMatcher.forJWSHeader(jwsHeader);JWKSelector jwkSelector new JWKSelector(jwkMatcher);ListJWK jwkMatches jwkSelector.select(loadJWKSByUrl(jwksUrl));// 获取公钥ListKey sanitizedKeyList new LinkedList();for (Key key : KeyConverter.toJavaKeys(jwkMatches)) {if (key instanceof PublicKey || key instanceof SecretKey) {sanitizedKeyList.add(key);}}ListIterator? extends Key it sanitizedKeyList.listIterator();JWSVerifierFactory jwsVerifierFactory new DefaultJWSVerifierFactory();while (it.hasNext()) {JWSVerifier verifier jwsVerifierFactory.createJWSVerifier(jwsHeader, it.next());if (verifier null) {continue;}final boolean validSignature signedJWT.verify(verifier);return validSignature;}return false;}