You can use this library to do that:
https://pub.dev/packages/googleapis_auth
Or you can use this one (as they recommend):
https://pub.dev/packages/extension_google_sign_in_as_googleapis_auth
import "package:googleapis_auth/auth_io.dart";
// Use service account credentials to get an authenticated and auto refreshing client.
Future<AuthClient> obtainAuthenticatedClient() async {
  final accountCredentials = ServiceAccountCredentials.fromJson({
    "private_key_id": "<please fill in>",
    "private_key": "<please fill in>",
    "client_email": "<please fill in>@developer.gserviceaccount.com",
    "client_id": "<please fill in>.apps.googleusercontent.com",
    "type": "service_account"
  });
  var scopes = ["https://www.googleapis.com/auth/firebase.messaging"];
  AuthClient client = await clientViaServiceAccount(accountCredentials, scopes);
  return client; // Remember to close the client when you are finished with it.
}
I created this class to handle it, using the recommended library.
class Messaging {
  // region Singleton
  static final Messaging _instance = Messaging._internal();
  Messaging._internal();
  static Messaging get instance => _instance;
//endregion
  static final String projectId = '<your-project-id>';
  static final String fcmUri =
      'https://fcm.googleapis.com/v1/projects/$projectId/messages:send';
  static final String messagingScope =
      'https://www.googleapis.com/auth/firebase.messaging';
  /// Credentials used to authenticate the http calls.
  /// If it is null, at the first time, it will be created.
  /// After that, we will check if the access token is valid.
  /// If it is expired, we are going to refresh it.
  /// We also need the client to refresh the token.
  AccessCredentials? _credentials;
  FirebaseMessaging get fcmInstance => FirebaseMessaging.instance;
  /// Sends a message to a topic or specific device
  /// [topic] might be a topic itself or a registered fcm token (the device token)
  Future<String> sendMessage({
    required String title,
    required String message,
    required String topic,
  }) async {
    Map<String, dynamic> notification = Map();
    notification['title'] = title;
    notification['body'] = message;
    Map<String, dynamic> body = Map();
    body["message"] = {
      'topic': topic,
      'notification': notification,
    };
    return _post(body);
  }
  /// Send data to the informed destionation
  /// [data] must have a maximum 3kb
  Future<String> sendData({
    required String topic,
    required Map<String, String> data,
  }) async {
    debugPrint('Send data to topic $topic: ');
    Map<String, dynamic> body = Map();
    body['message'] = {
      'topic': topic,
      'data': data,
    };
    return _post(body);
  }
  /// Posts the message
  Future<String> _post(Map<String, dynamic> body) async {
    Map<String, String> headers = await _buildHeaders();
    return http
        .post(
      Uri.parse(fcmUri),
      headers: headers,
      body: json.encode(body),
    )
        .then((http.Response response) {
      debugPrint(response.body);
      return response.body;
    });
  }
  /// Builds default header
  Future<Map<String, String>> _buildHeaders() async {
    await _autoRefreshCredentialsInitialize();
    String token = _credentials!.accessToken.data;
    Map<String, String> headers = Map();
    headers["Authorization"] = 'Bearer $token';
    headers["Content-Type"] = 'application/json';
    return headers;
  }
  /// Use service account credentials to obtain oauth credentials.
  /// Whenever new credentials are available, [_credentials] is updated automatically
  Future<void> _autoRefreshCredentialsInitialize() async {
    if (_credentials != null) {
      return;
    }
    // [Assets.files.serviceAccount] is the path to the json created on Services Account
    // Assets is a class that I created to access easily files on assets folder.
    String source = await Assets.readString(Assets.files.serviceAccount);
    final serviceAccount = jsonDecode(source);
    var accountCredentials = ServiceAccountCredentials.fromJson(serviceAccount);
    AutoRefreshingAuthClient autoRefreshingAuthClient =
        await clientViaServiceAccount(
      accountCredentials,
      [messagingScope],
    );
    /// initialization
    _credentials = autoRefreshingAuthClient.credentials;
    // when new credetials are available, _credentials will be updated
    // (it checks if the current credentials is expired)
    autoRefreshingAuthClient.credentialUpdates.listen((credentials) {
      _credentials = credentials;
    });
  }
  //region Registered Token
  Future<void> requestMessagingToken() async {
    // Get the token each time the application loads
    String? token = await fcmInstance.getToken();
    // Save the initial token to the database
    await _saveTokenToSharedPreference(token!);
    // Any time the token refreshes, store this in the database too.
    fcmInstance.onTokenRefresh.listen(_saveTokenToSharedPreference);
  }
  /// Verifica a necessidade de persistir o token
  Future<void> _saveTokenToSharedPreference(String token) async {
    FlutterSecureStorage prefs = FlutterSecureStorage();
    // verifica se é igual ao anterior
    String? prev = await prefs.read(key: 'tokenMessaging');
    if (prev == token) {
      debugPrint('Device registered!');
      return;
    }
    try {
      await prefs.write(key: "tokenMessaging", value: token);
      debugPrint('Firebase token saved!');
    } catch (e) {
      FirebaseCrashlytics.instance.recordError(e, null);
      print(e);
    }
  }
  Future<String?> get firebaseToken {
    FlutterSecureStorage prefs = FlutterSecureStorage();
    return prefs.read(key: 'tokenMessaging');
  }
//endregion
}