当使用private_key_jwt而不是client_secret_basic时,Spring Security中出现“无效客户端”

dzjeubhm  于 2023-03-12  发布在  Spring
关注(0)|答案(1)|浏览(169)

我尝试过从client_secret_basic切换到private_key_jwt,但是当我从auth提供程序返回时,我收到了以下错误:[invalid_client]客户端身份验证失败。未包括客户端身份验证
这不是一个Sping Boot 应用程序,但这是我目前所做的:

private ClientRegistration idPortenClientRegistration() {
  return ClientRegistrations
    .fromIssuerLocation("the endpoint")
    .clientId("the client id")
    .registrationId("idporten")
    .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
    .redirectUri("the redirect url")
    .scope(Arrays.asList("the scopes"))
    .userNameAttributeName(IdTokenClaimNames.SUB)
    .clientName("idporten")
    .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT);
    .build();
}

我的安全配置类:

http.oauth2Client(oauth2 -> oauth2
  .authorizationCodeGrant(codeGrant -> codeGrant
    .accessTokenResponseClient(accessTokenResponseClient())));

[...]

private DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient() {
  OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();
  requestEntityConverter.addParametersConverter(
  new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));

  DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
  tokenResponseClient.setRequestEntityConverter(requestEntityConverter);
  return tokenResponseClient;
}

private Function<ClientRegistration, JWK> jwkResolver = (clientRegistration) -> {
  if (clientRegistration.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
    JKSKeyManager keyManager = getApplicationContext().getBean("keyManager", JKSKeyManager.class);
    try {
      RSAPublicKey publicKey = (RSAPublicKey) keyManager.getPublicKey("idporten1");
      KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) keyManager.getKeyStore()
        .getEntry("idporten1", new KeyStore.PasswordProtection(keyEntryPassword1.toCharArray()));
      RSAPrivateKey privateKey = (RSAPrivateKey) pkEntry.getPrivateKey();
      return new RSAKey.Builder(publicKey).privateKey(privateKey).keyID(UUID.randomUUID().toString()).build();
    } catch (NoSuchAlgorithmException | UnrecoverableEntryException | KeyStoreException e) {
      logger.error("Failed to configure jwkResolver: " + e.getMessage(), e);
    }
  }
  return null;
};

如前所述,我成功地重定向到了auth提供程序,但是当我返回到应用程序时,收到了上面描述的错误。我还尝试记录accessTokenResponseClient()和jwkResolver。前一个方法在错误发生之前被调用,但是后一个方法没有记录任何内容。
提供商提供的一些文档:https://docs.digdir.no/oidc_protocol_token.htmlhttps://oidc-ver2.difi.no/idporten-oidc-provider/.well-known/openid-configuration

hgqdbh6s

hgqdbh6s1#

我想通了。NimbusJwtClientAuthenticationParametersConverter根本不起作用。x5ckid的声明只是被忽略了,所以我最终用了自己的转换器。而且它是如此的小巧和简单,所以我将在这里分享它。
但首先--我之前的代码中有一个错误,所以转换器从来没有被调用过。下面是你必须在安全配置中添加的内容:http.oauth2Login() .tokenEndpoint().accessTokenResponseClient(accessTokenResponseClient());
我的简单转换器只处理一个签名的JWT,不处理其他任何东西:

public class SignedJwtClientAuthenticationParametersConverter<T extends AbstractOAuth2AuthorizationGrantRequest>
        implements Converter<T, MultiValueMap<String, String>> {

    private static final String CLIENT_ASSERTION_TYPE_VALUE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";

    private final Function<ClientRegistration, String> jwt;

    public SignedJwtClientAuthenticationParametersConverter(
            final Function<ClientRegistration, String> jwt) {
        this.jwt = jwt;
    }

    @Override
    public MultiValueMap<String, String> convert(final T authorizationGrantRequest) {
        ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();

        String jwt = this.jwt.apply(clientRegistration);

        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
        parameters.set(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, CLIENT_ASSERTION_TYPE_VALUE);
        parameters.set(OAuth2ParameterNames.CLIENT_ASSERTION, jwt);

        return parameters;
    }
}

下面是accessTokenResponseClient bean:

@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
        OAuth2AuthorizationCodeGrantRequestEntityConverter requestEntityConverter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();

        requestEntityConverter.addParametersConverter(
                new SignedJwtClientAuthenticationParametersConverter<>(jwkResolver));

        DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
        accessTokenResponseClient.setRequestEntityConverter(requestEntityConverter);
        return accessTokenResponseClient;
    }

    private Function<ClientRegistration, String> jwkResolver = (clientRegistration) -> {
        if (clientRegistration.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.PRIVATE_KEY_JWT)) {
            try {
                return getJwtByClientRegistration(clientRegistration);
            } catch (Exception e) {
                logger.error("Failed to sign JWT: " + e.getMessage(), e);
            }
        }
        return null;
    };

现在显示的是方法getJwtByClientRegistration(),但这里我使用的是JOSE库,我更熟悉它。

相关问题