🔌 CHS Backend — Intégration Google API Client Services Java
Remplacement des appels décoratifs par les vrais clients Java Google — 3 APIs, 3 modules, 1 pom.xml
1
Dépendances Maven —
Dépendances Maven — pom.xml
<dependencies>
<!-- ============================================ -->
<!-- Google API Client Services (Java) -->
<!-- ============================================ -->
<!-- 🔍 Module 1 : Search Console (URL Inspection) -->
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-searchconsole</artifactId>
<version>v1-rev20240501-2.0.0</version>
</dependency>
<!-- 📨 Module 4 : Indexing API -->
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-indexing</artifactId>
<version>v3-rev20240501-2.0.0</version>
</dependency>
<!-- 📡 Module 6 : Custom Search JSON API -->
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-customsearch</artifactId>
<version>v1-rev20240501-2.0.0</version>
</dependency>
<!-- 🔐 Google Auth Library -->
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>1.23.0</version>
</dependency>
<!-- 📦 Jackson (JSON parsing) -->
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client-jackson2</artifactId>
<version>1.43.3</version>
</dependency>
</dependencies>
2
Authentification unifiée —
Authentification unifiée — GoogleAuthProvider.java
package com.chs.auth;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.List;
public class GoogleAuthProvider {
private static final JacksonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static GoogleCredentials credentials;
/**
* Charge les credentials OAuth2 depuis un compte de service.
*/
public static GoogleCredentials loadCredentials(String keyPath, List<String> scopes)
throws IOException {
credentials = ServiceAccountCredentials
.fromStream(new FileInputStream(keyPath))
.createScoped(scopes);
return credentials;
}
/**
* Retourne un HttpCredentialsAdapter réutilisable.
*/
public static HttpCredentialsAdapter getAdapter() {
if (credentials == null) {
throw new IllegalStateException("loadCredentials() doit être appelé d'abord");
}
return new HttpCredentialsAdapter(credentials);
}
public static JacksonFactory getJsonFactory() { return JSON_FACTORY; }
public static GoogleNetHttpTransport getTransport()
throws GeneralSecurityException, IOException {
return GoogleNetHttpTransport.newTrustedTransport();
}
}
3
Module 1 — Scanner Google URL Inspection API v1
| API | Search Console v1 — urlInspection.index.inspect |
| Scope OAuth | https://www.googleapis.com/auth/webmasters |
| Endpoint | POST /v1/urlInspection/index:inspect |
package com.chs.module1;
import com.chs.auth.GoogleAuthProvider;
import com.google.api.services.searchconsole.v1.SearchConsole;
import com.google.api.services.searchconsole.v1.model.*;
import java.io.IOException;
import java.security.GeneralSecurityException;
public class CanonicalScanner {
private final SearchConsole client;
private final String siteUrl;
public CanonicalScanner(String siteUrl) throws GeneralSecurityException, IOException {
this.siteUrl = siteUrl;
this.client = new SearchConsole.Builder(
GoogleAuthProvider.getTransport(),
GoogleAuthProvider.getJsonFactory(),
GoogleAuthProvider.getAdapter())
.setApplicationName("canonical-hijack-shield")
.build();
}
/**
* Inspecte une URL via l'API Search Console.
* Retourne le verdict canonique (userCanonical vs googleCanonical).
*/
public ScanResult inspect(String targetUrl) throws IOException {
InspectUrlIndexRequest request = new InspectUrlIndexRequest()
.setInspectionUrl(targetUrl)
.setSiteUrl(this.siteUrl)
.setLanguageCode("fr");
InspectUrlIndexResponse response = client
.urlInspection()
.index()
.inspect(request)
.execute();
IndexStatusResult result = response.getInspectionResult().getIndexStatusResult();
// 🔴 Détection du verrou canonique parasite
String userCanonical = result.getUserCanonical();
String googleCanonical = result.getGoogleCanonical();
boolean isParasited = userCanonical != null
&& googleCanonical != null
&& !userCanonical.equals(googleCanonical);
return new ScanResult(
result.getVerdict(),
result.getCoverageState(),
userCanonical,
googleCanonical,
isParasited ? "parameter_injection" : null,
isParasited,
result.getLastCrawlTime()
);
}
}
4
Module 4 — Indexing API v3 Ping Google après nettoyage
| API | Indexing API v3 — urlNotifications.publish |
| Scope OAuth | https://www.googleapis.com/auth/indexing |
| Endpoint | POST /v3/urlNotifications:publish |
package com.chs.module4;
import com.chs.auth.GoogleAuthProvider;
import com.google.api.services.indexing.v3.Indexing;
import com.google.api.services.indexing.v3.model.*;
import java.io.IOException;
import java.security.GeneralSecurityException;
public class IndexingNotifier {
private final Indexing client;
public IndexingNotifier() throws GeneralSecurityException, IOException {
this.client = new Indexing.Builder(
GoogleAuthProvider.getTransport(),
GoogleAuthProvider.getJsonFactory(),
GoogleAuthProvider.getAdapter())
.setApplicationName("canonical-hijack-shield")
.build();
}
/**
* Notifie Google que l'URL a été mise à jour (canonical corrigé).
* Équivaut à : POST /v3/urlNotifications:publish
* { "url": "...", "type": "URL_UPDATED" }
*/
public boolean notifyUrlUpdated(String url) throws IOException {
UrlNotification notification = new UrlNotification()
.setUrl(url)
.setType("URL_UPDATED");
UrlNotificationMetadata response = client
.urlNotifications()
.publish(notification)
.execute();
// ✅ 200 OK → Google a bien reçu la notification
return response != null
&& response.getLatestUpdate() != null
&& response.getLatestUpdate().getUrl().equals(url);
}
/**
* Notifie que l'URL a été supprimée (si nécessaire).
*/
public boolean notifyUrlRemoved(String url) throws IOException {
UrlNotification notification = new UrlNotification()
.setUrl(url)
.setType("URL_DELETED");
UrlNotificationMetadata response = client
.urlNotifications()
.publish(notification)
.execute();
return response != null;
}
}
5
Module 6 — Custom Search JSON API v1 Surveillance SERP
| API | Custom Search JSON API v1 |
| Auth | API Key (GOOGLE_API_KEY) + Search Engine ID (CX) |
| Endpoint | GET /customsearch/v1?key=...&cx=...&q=... |
package com.chs.module6;
import com.chs.auth.GoogleAuthProvider;
import com.google.api.services.customsearch.v1.CustomSearchAPI;
import com.google.api.services.customsearch.v1.model.*;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.List;
public class SerpMonitor {
private final CustomSearchAPI client;
private final String cx; // Search Engine ID (configuré dans cse.google.com)
public SerpMonitor(String apiKey, String cx) throws GeneralSecurityException, IOException {
this.cx = cx;
this.client = new CustomSearchAPI.Builder(
GoogleAuthProvider.getTransport(),
GoogleAuthProvider.getJsonFactory(),
request -> { /* pas d'auth OAuth ici, on utilise la clé API en paramètre */ })
.setApplicationName("canonical-hijack-shield")
.build();
}
/**
* Surveillance SERP : vérifie si des URLs parasites apparaissent
* pour une requête donnée sur un domaine spécifique.
*/
public MonitorResult monitor(String domain, String keyword, String apiKey) throws IOException {
String query = String.format("site:%s \"%s\"", domain, keyword);
CustomSearchAPI.Cse.List request = client.cse().list()
.setCx(this.cx)
.setKey(apiKey)
.setQ(query)
.setLr("lang_fr");
Search results = request.execute();
List<SerpEntry> cleanUrls = new ArrayList<>();
List<SerpEntry> parasiteUrls = new ArrayList<>();
if (results.getItems() != null) {
for (int i = 0; i < results.getItems().size(); i++) {
Result item = results.getItems().get(i);
String link = item.getLink();
// 🔴 Détection d'URL parasite (paramètres suspects)
boolean isParasite = link.contains("?parasite=")
|| link.contains("?utm_source=")
|| link.contains("?gclid=")
|| link.contains("?fbclid=");
SerpEntry entry = new SerpEntry(i + 1, link, isParasite);
if (isParasite) {
parasiteUrls.add(entry);
} else {
cleanUrls.add(entry);
}
}
}
boolean alertTriggered = !parasiteUrls.isEmpty();
return new MonitorResult(
results.getSearchInformation() != null
? results.getSearchInformation().getTotalResults() : 0,
cleanUrls,
parasiteUrls,
alertTriggered ? "degraded" : "clean",
alertTriggered
);
}
}
6
Bootstrap —
Bootstrap — ChsApplication.java
package com.chs;
import com.chs.auth.GoogleAuthProvider;
import com.chs.module1.CanonicalScanner;
import com.chs.module1.ScanResult;
import com.chs.module4.IndexingNotifier;
import com.chs.module6.MonitorResult;
import com.chs.module6.SerpMonitor;
import com.google.auth.oauth2.GoogleCredentials;
import java.util.List;
public class ChsApplication {
public static void main(String[] args) throws Exception {
// 🔐 1. Charger les credentials
String keyPath = System.getenv("CHS_GOOGLE_CREDENTIALS");
GoogleAuthProvider.loadCredentials(keyPath, List.of(
"https://www.googleapis.com/auth/webmasters", // M1
"https://www.googleapis.com/auth/indexing" // M4
));
// 🔍 2. Module 1 — Scanner
CanonicalScanner scanner = new CanonicalScanner(
"sc-domain:mediapart.fr");
ScanResult scan = scanner.inspect(
"https://www.mediapart.fr/journal/france/270917/la-nouvelle-recherche-de-financement");
System.out.println("📋 Verdict: " + scan.verdict());
System.out.println("🔴 Parasité: " + scan.isParasited());
System.out.println("📐 User Canonical: " + scan.userCanonical());
System.out.println("⚠️ Google Canonical: " + scan.googleCanonical());
// 🔧 3. Module 4 — Si parasité, on nettoie + ping
if (scan.isParasited()) {
IndexingNotifier notifier = new IndexingNotifier();
boolean ok = notifier.notifyUrlUpdated(scan.userCanonical());
System.out.println("📨 Indexing API ping → " + (ok ? "✅ OK" : "❌ Échec"));
}
// 📡 4. Module 6 — Surveillance
String apiKey = System.getenv("CHS_GOOGLE_API_KEY");
String cx = System.getenv("CHS_CSE_ID");
SerpMonitor monitor = new SerpMonitor(apiKey, cx);
MonitorResult mon = monitor.monitor("mediapart.fr", "fortune", apiKey);
System.out.println("📡 SERP Status: " + mon.status());
System.out.println("🧹 Clean URLs: " + mon.cleanUrls().size());
System.out.println("🦠 Parasite URLs: " + mon.parasiteUrls().size());
System.out.println("🚨 Alerte: " + mon.alertTriggered());
}
}
7
Récapitulatif — Mapping Modules ↔ APIs Google
| Module CHS | Google API | Dépendance Maven | Scope OAuth |
|---|---|---|---|
| M1 Scanner | Search Console v1 | google-api-services-searchconsole |
webmasters |
| M4 Remediation | Indexing API v3 | google-api-services-indexing |
indexing |
| M6 Monitoring | Custom Search JSON v1 | google-api-services-customsearch |
API Key (pas OAuth) |